aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/ptp
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ptp')
-rw-r--r--drivers/ptp/Kconfig67
-rw-r--r--drivers/ptp/Makefile7
-rw-r--r--drivers/ptp/idt8a340_reg.h661
-rw-r--r--drivers/ptp/ptp_chardev.c56
-rw-r--r--drivers/ptp/ptp_clock.c140
-rw-r--r--drivers/ptp/ptp_clockmatrix.c2041
-rw-r--r--drivers/ptp/ptp_clockmatrix.h124
-rw-r--r--drivers/ptp/ptp_dte.c14
-rw-r--r--drivers/ptp/ptp_idt82p33.c944
-rw-r--r--drivers/ptp/ptp_idt82p33.h97
-rw-r--r--drivers/ptp/ptp_ines.c124
-rw-r--r--drivers/ptp/ptp_kvm_arm.c28
-rw-r--r--drivers/ptp/ptp_kvm_common.c (renamed from drivers/ptp/ptp_kvm.c)87
-rw-r--r--drivers/ptp/ptp_kvm_x86.c92
-rw-r--r--drivers/ptp/ptp_ocp.c3885
-rw-r--r--drivers/ptp/ptp_pch.c248
-rw-r--r--drivers/ptp/ptp_private.h50
-rw-r--r--drivers/ptp/ptp_qoriq.c62
-rw-r--r--drivers/ptp/ptp_sysfs.c167
-rw-r--r--drivers/ptp/ptp_vclock.c295
-rw-r--r--drivers/ptp/ptp_vmw.c144
21 files changed, 7710 insertions, 1623 deletions
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig
index 475c60dccaa4..fe4971b65c64 100644
--- a/drivers/ptp/Kconfig
+++ b/drivers/ptp/Kconfig
@@ -8,6 +8,7 @@ menu "PTP clock support"
config PTP_1588_CLOCK
tristate "PTP clock support"
depends on NET && POSIX_TIMERS
+ default ETHERNET
select PPS
select NET_PTP_CLASSIFY
help
@@ -26,6 +27,18 @@ config PTP_1588_CLOCK
To compile this driver as a module, choose M here: the module
will be called ptp.
+config PTP_1588_CLOCK_OPTIONAL
+ tristate
+ default y if PTP_1588_CLOCK=n
+ default PTP_1588_CLOCK
+ help
+ Drivers that can optionally use the PTP_1588_CLOCK framework
+ should depend on this symbol to prevent them from being built
+ into vmlinux while the PTP support itself is in a loadable
+ module.
+ If PTP support is disabled, this dependency will still be
+ met, and drivers refer to dummy helpers.
+
config PTP_1588_CLOCK_DTE
tristate "Broadcom DTE as PTP clock"
depends on PTP_1588_CLOCK
@@ -64,7 +77,8 @@ config DP83640_PHY
depends on NETWORK_PHY_TIMESTAMPING
depends on PHYLIB
depends on PTP_1588_CLOCK
- ---help---
+ select CRC32
+ help
Supports the DP83640 PHYTER with IEEE 1588 features.
This driver adds support for using the DP83640 as a PTP
@@ -78,6 +92,7 @@ config DP83640_PHY
config PTP_1588_CLOCK_INES
tristate "ZHAW InES PTP time stamping IP core"
depends on NETWORK_PHY_TIMESTAMPING
+ depends on HAS_IOMEM
depends on PHYLIB
depends on PTP_1588_CLOCK
help
@@ -88,8 +103,9 @@ config PTP_1588_CLOCK_INES
config PTP_1588_CLOCK_PCH
tristate "Intel PCH EG20T as PTP clock"
depends on X86_32 || COMPILE_TEST
- depends on HAS_IOMEM && NET
- imply PTP_1588_CLOCK
+ depends on HAS_IOMEM && PCI
+ depends on NET
+ depends on PTP_1588_CLOCK
help
This driver adds support for using the PCH EG20T as a PTP
clock. The hardware supports time stamping of PTP packets
@@ -106,7 +122,7 @@ config PTP_1588_CLOCK_PCH
config PTP_1588_CLOCK_KVM
tristate "KVM virtual PTP clock"
depends on PTP_1588_CLOCK
- depends on KVM_GUEST && X86
+ depends on (KVM_GUEST && X86) || (HAVE_ARM_SMCCC_DISCOVERY && ARM_ARCH_TIMER)
default y
help
This driver adds support for using kvm infrastructure as a PTP
@@ -115,6 +131,18 @@ config PTP_1588_CLOCK_KVM
To compile this driver as a module, choose M here: the module
will be called ptp_kvm.
+config PTP_1588_CLOCK_IDT82P33
+ tristate "IDT 82P33xxx PTP clock"
+ depends on PTP_1588_CLOCK && I2C
+ default n
+ help
+ This driver adds support for using the IDT 82P33xxx as a PTP
+ clock. This clock is only useful if your time stamping MAC
+ is connected to the IDT chip.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ptp_idt82p33.
+
config PTP_1588_CLOCK_IDTCM
tristate "IDT CLOCKMATRIX as PTP clock"
depends on PTP_1588_CLOCK && I2C
@@ -127,4 +155,35 @@ config PTP_1588_CLOCK_IDTCM
To compile this driver as a module, choose M here: the module
will be called ptp_clockmatrix.
+config PTP_1588_CLOCK_VMW
+ tristate "VMware virtual PTP clock"
+ depends on ACPI && HYPERVISOR_GUEST && X86
+ depends on PTP_1588_CLOCK
+ help
+ This driver adds support for using VMware virtual precision
+ clock device as a PTP clock. This is only useful in virtual
+ machines running on VMware virtual infrastructure.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ptp_vmw.
+
+config PTP_1588_CLOCK_OCP
+ tristate "OpenCompute TimeCard as PTP clock"
+ depends on PTP_1588_CLOCK
+ depends on HAS_IOMEM && PCI
+ depends on I2C && MTD
+ depends on SERIAL_8250
+ depends on !S390
+ depends on COMMON_CLK
+ select NET_DEVLINK
+ select CRC16
+ help
+ This driver adds support for an OpenCompute time card.
+
+ The OpenCompute time card is an atomic clock along with
+ a GPS receiver that provides a Grandmaster clock source
+ for a PTP enabled network.
+
+ More information is available at http://www.timingcard.com/
+
endmenu
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile
index 8c830336f178..28a6fe342d3e 100644
--- a/drivers/ptp/Makefile
+++ b/drivers/ptp/Makefile
@@ -3,7 +3,9 @@
# Makefile for PTP 1588 clock support.
#
-ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o
+ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o ptp_vclock.o
+ptp_kvm-$(CONFIG_X86) := ptp_kvm_x86.o ptp_kvm_common.o
+ptp_kvm-$(CONFIG_HAVE_ARM_SMCCC) := ptp_kvm_arm.o ptp_kvm_common.o
obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o
obj-$(CONFIG_PTP_1588_CLOCK_DTE) += ptp_dte.o
obj-$(CONFIG_PTP_1588_CLOCK_INES) += ptp_ines.o
@@ -13,3 +15,6 @@ obj-$(CONFIG_PTP_1588_CLOCK_QORIQ) += ptp-qoriq.o
ptp-qoriq-y += ptp_qoriq.o
ptp-qoriq-$(CONFIG_DEBUG_FS) += ptp_qoriq_debugfs.o
obj-$(CONFIG_PTP_1588_CLOCK_IDTCM) += ptp_clockmatrix.o
+obj-$(CONFIG_PTP_1588_CLOCK_IDT82P33) += ptp_idt82p33.o
+obj-$(CONFIG_PTP_1588_CLOCK_VMW) += ptp_vmw.o
+obj-$(CONFIG_PTP_1588_CLOCK_OCP) += ptp_ocp.o
diff --git a/drivers/ptp/idt8a340_reg.h b/drivers/ptp/idt8a340_reg.h
deleted file mode 100644
index 69eedda9f731..000000000000
--- a/drivers/ptp/idt8a340_reg.h
+++ /dev/null
@@ -1,661 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ */
-/* idt8a340_reg.h
- *
- * Originally generated by regen.tcl on Thu Feb 14 19:23:44 PST 2019
- * https://github.com/richardcochran/regen
- *
- * Hand modified to include some HW registers.
- * Based on 4.8.0, SCSR rev C commit a03c7ae5
- */
-#ifndef HAVE_IDT8A340_REG
-#define HAVE_IDT8A340_REG
-
-#define PAGE_ADDR_BASE 0x0000
-#define PAGE_ADDR 0x00fc
-
-#define HW_REVISION 0x8180
-#define REV_ID 0x007a
-
-#define HW_DPLL_0 (0x8a00)
-#define HW_DPLL_1 (0x8b00)
-#define HW_DPLL_2 (0x8c00)
-#define HW_DPLL_3 (0x8d00)
-
-#define HW_DPLL_TOD_SW_TRIG_ADDR__0 (0x080)
-#define HW_DPLL_TOD_CTRL_1 (0x089)
-#define HW_DPLL_TOD_CTRL_2 (0x08A)
-#define HW_DPLL_TOD_OVR__0 (0x098)
-#define HW_DPLL_TOD_OUT_0__0 (0x0B0)
-
-#define HW_Q0_Q1_CH_SYNC_CTRL_0 (0xa740)
-#define HW_Q0_Q1_CH_SYNC_CTRL_1 (0xa741)
-#define HW_Q2_Q3_CH_SYNC_CTRL_0 (0xa742)
-#define HW_Q2_Q3_CH_SYNC_CTRL_1 (0xa743)
-#define HW_Q4_Q5_CH_SYNC_CTRL_0 (0xa744)
-#define HW_Q4_Q5_CH_SYNC_CTRL_1 (0xa745)
-#define HW_Q6_Q7_CH_SYNC_CTRL_0 (0xa746)
-#define HW_Q6_Q7_CH_SYNC_CTRL_1 (0xa747)
-#define HW_Q8_CH_SYNC_CTRL_0 (0xa748)
-#define HW_Q8_CH_SYNC_CTRL_1 (0xa749)
-#define HW_Q9_CH_SYNC_CTRL_0 (0xa74a)
-#define HW_Q9_CH_SYNC_CTRL_1 (0xa74b)
-#define HW_Q10_CH_SYNC_CTRL_0 (0xa74c)
-#define HW_Q10_CH_SYNC_CTRL_1 (0xa74d)
-#define HW_Q11_CH_SYNC_CTRL_0 (0xa74e)
-#define HW_Q11_CH_SYNC_CTRL_1 (0xa74f)
-
-#define SYNC_SOURCE_DPLL0_TOD_PPS 0x14
-#define SYNC_SOURCE_DPLL1_TOD_PPS 0x15
-#define SYNC_SOURCE_DPLL2_TOD_PPS 0x16
-#define SYNC_SOURCE_DPLL3_TOD_PPS 0x17
-
-#define SYNCTRL1_MASTER_SYNC_RST BIT(7)
-#define SYNCTRL1_MASTER_SYNC_TRIG BIT(5)
-#define SYNCTRL1_TOD_SYNC_TRIG BIT(4)
-#define SYNCTRL1_FBDIV_FRAME_SYNC_TRIG BIT(3)
-#define SYNCTRL1_FBDIV_SYNC_TRIG BIT(2)
-#define SYNCTRL1_Q1_DIV_SYNC_TRIG BIT(1)
-#define SYNCTRL1_Q0_DIV_SYNC_TRIG BIT(0)
-
-#define RESET_CTRL 0xc000
-#define SM_RESET 0x0012
-#define SM_RESET_CMD 0x5A
-
-#define GENERAL_STATUS 0xc014
-#define HW_REV_ID 0x000A
-#define BOND_ID 0x000B
-#define HW_CSR_ID 0x000C
-#define HW_IRQ_ID 0x000E
-
-#define MAJ_REL 0x0010
-#define MIN_REL 0x0011
-#define HOTFIX_REL 0x0012
-
-#define PIPELINE_ID 0x0014
-#define BUILD_ID 0x0018
-
-#define JTAG_DEVICE_ID 0x001c
-#define PRODUCT_ID 0x001e
-
-#define OTP_SCSR_CONFIG_SELECT 0x0022
-
-#define STATUS 0xc03c
-#define USER_GPIO0_TO_7_STATUS 0x008a
-#define USER_GPIO8_TO_15_STATUS 0x008b
-
-#define GPIO_USER_CONTROL 0xc160
-#define GPIO0_TO_7_OUT 0x0000
-#define GPIO8_TO_15_OUT 0x0001
-
-#define STICKY_STATUS_CLEAR 0xc164
-
-#define GPIO_TOD_NOTIFICATION_CLEAR 0xc16c
-
-#define ALERT_CFG 0xc188
-
-#define SYS_DPLL_XO 0xc194
-
-#define SYS_APLL 0xc19c
-
-#define INPUT_0 0xc1b0
-
-#define INPUT_1 0xc1c0
-
-#define INPUT_2 0xc1d0
-
-#define INPUT_3 0xc200
-
-#define INPUT_4 0xc210
-
-#define INPUT_5 0xc220
-
-#define INPUT_6 0xc230
-
-#define INPUT_7 0xc240
-
-#define INPUT_8 0xc250
-
-#define INPUT_9 0xc260
-
-#define INPUT_10 0xc280
-
-#define INPUT_11 0xc290
-
-#define INPUT_12 0xc2a0
-
-#define INPUT_13 0xc2b0
-
-#define INPUT_14 0xc2c0
-
-#define INPUT_15 0xc2d0
-
-#define REF_MON_0 0xc2e0
-
-#define REF_MON_1 0xc2ec
-
-#define REF_MON_2 0xc300
-
-#define REF_MON_3 0xc30c
-
-#define REF_MON_4 0xc318
-
-#define REF_MON_5 0xc324
-
-#define REF_MON_6 0xc330
-
-#define REF_MON_7 0xc33c
-
-#define REF_MON_8 0xc348
-
-#define REF_MON_9 0xc354
-
-#define REF_MON_10 0xc360
-
-#define REF_MON_11 0xc36c
-
-#define REF_MON_12 0xc380
-
-#define REF_MON_13 0xc38c
-
-#define REF_MON_14 0xc398
-
-#define REF_MON_15 0xc3a4
-
-#define DPLL_0 0xc3b0
-#define DPLL_CTRL_REG_0 0x0002
-#define DPLL_CTRL_REG_1 0x0003
-#define DPLL_CTRL_REG_2 0x0004
-#define DPLL_TOD_SYNC_CFG 0x0031
-#define DPLL_COMBO_SLAVE_CFG_0 0x0032
-#define DPLL_COMBO_SLAVE_CFG_1 0x0033
-#define DPLL_SLAVE_REF_CFG 0x0034
-#define DPLL_REF_MODE 0x0035
-#define DPLL_PHASE_MEASUREMENT_CFG 0x0036
-#define DPLL_MODE 0x0037
-
-#define DPLL_1 0xc400
-
-#define DPLL_2 0xc438
-
-#define DPLL_3 0xc480
-
-#define DPLL_4 0xc4b8
-
-#define DPLL_5 0xc500
-
-#define DPLL_6 0xc538
-
-#define DPLL_7 0xc580
-
-#define SYS_DPLL 0xc5b8
-
-#define DPLL_CTRL_0 0xc600
-#define DPLL_CTRL_DPLL_MANU_REF_CFG 0x0001
-
-#define DPLL_CTRL_1 0xc63c
-
-#define DPLL_CTRL_2 0xc680
-
-#define DPLL_CTRL_3 0xc6bc
-
-#define DPLL_CTRL_4 0xc700
-
-#define DPLL_CTRL_5 0xc73c
-
-#define DPLL_CTRL_6 0xc780
-
-#define DPLL_CTRL_7 0xc7bc
-
-#define SYS_DPLL_CTRL 0xc800
-
-#define DPLL_PHASE_0 0xc818
-
-/* Signed 42-bit FFO in units of 2^(-53) */
-#define DPLL_WR_PHASE 0x0000
-
-#define DPLL_PHASE_1 0xc81c
-
-#define DPLL_PHASE_2 0xc820
-
-#define DPLL_PHASE_3 0xc824
-
-#define DPLL_PHASE_4 0xc828
-
-#define DPLL_PHASE_5 0xc82c
-
-#define DPLL_PHASE_6 0xc830
-
-#define DPLL_PHASE_7 0xc834
-
-#define DPLL_FREQ_0 0xc838
-
-/* Signed 42-bit FFO in units of 2^(-53) */
-#define DPLL_WR_FREQ 0x0000
-
-#define DPLL_FREQ_1 0xc840
-
-#define DPLL_FREQ_2 0xc848
-
-#define DPLL_FREQ_3 0xc850
-
-#define DPLL_FREQ_4 0xc858
-
-#define DPLL_FREQ_5 0xc860
-
-#define DPLL_FREQ_6 0xc868
-
-#define DPLL_FREQ_7 0xc870
-
-#define DPLL_PHASE_PULL_IN_0 0xc880
-#define PULL_IN_OFFSET 0x0000 /* Signed 32 bit */
-#define PULL_IN_SLOPE_LIMIT 0x0004 /* Unsigned 24 bit */
-#define PULL_IN_CTRL 0x0007
-
-#define DPLL_PHASE_PULL_IN_1 0xc888
-
-#define DPLL_PHASE_PULL_IN_2 0xc890
-
-#define DPLL_PHASE_PULL_IN_3 0xc898
-
-#define DPLL_PHASE_PULL_IN_4 0xc8a0
-
-#define DPLL_PHASE_PULL_IN_5 0xc8a8
-
-#define DPLL_PHASE_PULL_IN_6 0xc8b0
-
-#define DPLL_PHASE_PULL_IN_7 0xc8b8
-
-#define GPIO_CFG 0xc8c0
-#define GPIO_CFG_GBL 0x0000
-
-#define GPIO_0 0xc8c2
-#define GPIO_DCO_INC_DEC 0x0000
-#define GPIO_OUT_CTRL_0 0x0001
-#define GPIO_OUT_CTRL_1 0x0002
-#define GPIO_TOD_TRIG 0x0003
-#define GPIO_DPLL_INDICATOR 0x0004
-#define GPIO_LOS_INDICATOR 0x0005
-#define GPIO_REF_INPUT_DSQ_0 0x0006
-#define GPIO_REF_INPUT_DSQ_1 0x0007
-#define GPIO_REF_INPUT_DSQ_2 0x0008
-#define GPIO_REF_INPUT_DSQ_3 0x0009
-#define GPIO_MAN_CLK_SEL_0 0x000a
-#define GPIO_MAN_CLK_SEL_1 0x000b
-#define GPIO_MAN_CLK_SEL_2 0x000c
-#define GPIO_SLAVE 0x000d
-#define GPIO_ALERT_OUT_CFG 0x000e
-#define GPIO_TOD_NOTIFICATION_CFG 0x000f
-#define GPIO_CTRL 0x0010
-
-#define GPIO_1 0xc8d4
-
-#define GPIO_2 0xc8e6
-
-#define GPIO_3 0xc900
-
-#define GPIO_4 0xc912
-
-#define GPIO_5 0xc924
-
-#define GPIO_6 0xc936
-
-#define GPIO_7 0xc948
-
-#define GPIO_8 0xc95a
-
-#define GPIO_9 0xc980
-
-#define GPIO_10 0xc992
-
-#define GPIO_11 0xc9a4
-
-#define GPIO_12 0xc9b6
-
-#define GPIO_13 0xc9c8
-
-#define GPIO_14 0xc9da
-
-#define GPIO_15 0xca00
-
-#define OUT_DIV_MUX 0xca12
-
-#define OUTPUT_0 0xca14
-/* FOD frequency output divider value */
-#define OUT_DIV 0x0000
-#define OUT_DUTY_CYCLE_HIGH 0x0004
-#define OUT_CTRL_0 0x0008
-#define OUT_CTRL_1 0x0009
-/* Phase adjustment in FOD cycles */
-#define OUT_PHASE_ADJ 0x000c
-
-#define OUTPUT_1 0xca24
-
-#define OUTPUT_2 0xca34
-
-#define OUTPUT_3 0xca44
-
-#define OUTPUT_4 0xca54
-
-#define OUTPUT_5 0xca64
-
-#define OUTPUT_6 0xca80
-
-#define OUTPUT_7 0xca90
-
-#define OUTPUT_8 0xcaa0
-
-#define OUTPUT_9 0xcab0
-
-#define OUTPUT_10 0xcac0
-
-#define OUTPUT_11 0xcad0
-
-#define SERIAL 0xcae0
-
-#define PWM_ENCODER_0 0xcb00
-
-#define PWM_ENCODER_1 0xcb08
-
-#define PWM_ENCODER_2 0xcb10
-
-#define PWM_ENCODER_3 0xcb18
-
-#define PWM_ENCODER_4 0xcb20
-
-#define PWM_ENCODER_5 0xcb28
-
-#define PWM_ENCODER_6 0xcb30
-
-#define PWM_ENCODER_7 0xcb38
-
-#define PWM_DECODER_0 0xcb40
-
-#define PWM_DECODER_1 0xcb48
-
-#define PWM_DECODER_2 0xcb50
-
-#define PWM_DECODER_3 0xcb58
-
-#define PWM_DECODER_4 0xcb60
-
-#define PWM_DECODER_5 0xcb68
-
-#define PWM_DECODER_6 0xcb70
-
-#define PWM_DECODER_7 0xcb80
-
-#define PWM_DECODER_8 0xcb88
-
-#define PWM_DECODER_9 0xcb90
-
-#define PWM_DECODER_10 0xcb98
-
-#define PWM_DECODER_11 0xcba0
-
-#define PWM_DECODER_12 0xcba8
-
-#define PWM_DECODER_13 0xcbb0
-
-#define PWM_DECODER_14 0xcbb8
-
-#define PWM_DECODER_15 0xcbc0
-
-#define PWM_USER_DATA 0xcbc8
-
-#define TOD_0 0xcbcc
-
-/* Enable TOD counter, output channel sync and even-PPS mode */
-#define TOD_CFG 0x0000
-
-#define TOD_1 0xcbce
-
-#define TOD_2 0xcbd0
-
-#define TOD_3 0xcbd2
-
-
-#define TOD_WRITE_0 0xcc00
-/* 8-bit subns, 32-bit ns, 48-bit seconds */
-#define TOD_WRITE 0x0000
-/* Counter increments after TOD write is completed */
-#define TOD_WRITE_COUNTER 0x000c
-/* TOD write trigger configuration */
-#define TOD_WRITE_SELECT_CFG_0 0x000d
-/* TOD write trigger selection */
-#define TOD_WRITE_CMD 0x000f
-
-#define TOD_WRITE_1 0xcc10
-
-#define TOD_WRITE_2 0xcc20
-
-#define TOD_WRITE_3 0xcc30
-
-#define TOD_READ_PRIMARY_0 0xcc40
-/* 8-bit subns, 32-bit ns, 48-bit seconds */
-#define TOD_READ_PRIMARY 0x0000
-/* Counter increments after TOD write is completed */
-#define TOD_READ_PRIMARY_COUNTER 0x000b
-/* Read trigger configuration */
-#define TOD_READ_PRIMARY_SEL_CFG_0 0x000c
-/* Read trigger selection */
-#define TOD_READ_PRIMARY_CMD 0x000e
-
-#define TOD_READ_PRIMARY_1 0xcc50
-
-#define TOD_READ_PRIMARY_2 0xcc60
-
-#define TOD_READ_PRIMARY_3 0xcc80
-
-#define TOD_READ_SECONDARY_0 0xcc90
-
-#define TOD_READ_SECONDARY_1 0xcca0
-
-#define TOD_READ_SECONDARY_2 0xccb0
-
-#define TOD_READ_SECONDARY_3 0xccc0
-
-#define OUTPUT_TDC_CFG 0xccd0
-
-#define OUTPUT_TDC_0 0xcd00
-
-#define OUTPUT_TDC_1 0xcd08
-
-#define OUTPUT_TDC_2 0xcd10
-
-#define OUTPUT_TDC_3 0xcd18
-
-#define INPUT_TDC 0xcd20
-
-#define SCRATCH 0xcf50
-
-#define EEPROM 0xcf68
-
-#define OTP 0xcf70
-
-#define BYTE 0xcf80
-
-/* Bit definitions for the MAJ_REL register */
-#define MAJOR_SHIFT (1)
-#define MAJOR_MASK (0x7f)
-#define PR_BUILD BIT(0)
-
-/* Bit definitions for the USER_GPIO0_TO_7_STATUS register */
-#define GPIO0_LEVEL BIT(0)
-#define GPIO1_LEVEL BIT(1)
-#define GPIO2_LEVEL BIT(2)
-#define GPIO3_LEVEL BIT(3)
-#define GPIO4_LEVEL BIT(4)
-#define GPIO5_LEVEL BIT(5)
-#define GPIO6_LEVEL BIT(6)
-#define GPIO7_LEVEL BIT(7)
-
-/* Bit definitions for the USER_GPIO8_TO_15_STATUS register */
-#define GPIO8_LEVEL BIT(0)
-#define GPIO9_LEVEL BIT(1)
-#define GPIO10_LEVEL BIT(2)
-#define GPIO11_LEVEL BIT(3)
-#define GPIO12_LEVEL BIT(4)
-#define GPIO13_LEVEL BIT(5)
-#define GPIO14_LEVEL BIT(6)
-#define GPIO15_LEVEL BIT(7)
-
-/* Bit definitions for the GPIO0_TO_7_OUT register */
-#define GPIO0_DRIVE_LEVEL BIT(0)
-#define GPIO1_DRIVE_LEVEL BIT(1)
-#define GPIO2_DRIVE_LEVEL BIT(2)
-#define GPIO3_DRIVE_LEVEL BIT(3)
-#define GPIO4_DRIVE_LEVEL BIT(4)
-#define GPIO5_DRIVE_LEVEL BIT(5)
-#define GPIO6_DRIVE_LEVEL BIT(6)
-#define GPIO7_DRIVE_LEVEL BIT(7)
-
-/* Bit definitions for the GPIO8_TO_15_OUT register */
-#define GPIO8_DRIVE_LEVEL BIT(0)
-#define GPIO9_DRIVE_LEVEL BIT(1)
-#define GPIO10_DRIVE_LEVEL BIT(2)
-#define GPIO11_DRIVE_LEVEL BIT(3)
-#define GPIO12_DRIVE_LEVEL BIT(4)
-#define GPIO13_DRIVE_LEVEL BIT(5)
-#define GPIO14_DRIVE_LEVEL BIT(6)
-#define GPIO15_DRIVE_LEVEL BIT(7)
-
-/* Bit definitions for the DPLL_TOD_SYNC_CFG register */
-#define TOD_SYNC_SOURCE_SHIFT (1)
-#define TOD_SYNC_SOURCE_MASK (0x3)
-#define TOD_SYNC_EN BIT(0)
-
-/* Bit definitions for the DPLL_MODE register */
-#define WRITE_TIMER_MODE BIT(6)
-#define PLL_MODE_SHIFT (3)
-#define PLL_MODE_MASK (0x7)
-#define STATE_MODE_SHIFT (0)
-#define STATE_MODE_MASK (0x7)
-
-/* Bit definitions for the GPIO_CFG_GBL register */
-#define SUPPLY_MODE_SHIFT (0)
-#define SUPPLY_MODE_MASK (0x3)
-
-/* Bit definitions for the GPIO_DCO_INC_DEC register */
-#define INCDEC_DPLL_INDEX_SHIFT (0)
-#define INCDEC_DPLL_INDEX_MASK (0x7)
-
-/* Bit definitions for the GPIO_OUT_CTRL_0 register */
-#define CTRL_OUT_0 BIT(0)
-#define CTRL_OUT_1 BIT(1)
-#define CTRL_OUT_2 BIT(2)
-#define CTRL_OUT_3 BIT(3)
-#define CTRL_OUT_4 BIT(4)
-#define CTRL_OUT_5 BIT(5)
-#define CTRL_OUT_6 BIT(6)
-#define CTRL_OUT_7 BIT(7)
-
-/* Bit definitions for the GPIO_OUT_CTRL_1 register */
-#define CTRL_OUT_8 BIT(0)
-#define CTRL_OUT_9 BIT(1)
-#define CTRL_OUT_10 BIT(2)
-#define CTRL_OUT_11 BIT(3)
-#define CTRL_OUT_12 BIT(4)
-#define CTRL_OUT_13 BIT(5)
-#define CTRL_OUT_14 BIT(6)
-#define CTRL_OUT_15 BIT(7)
-
-/* Bit definitions for the GPIO_TOD_TRIG register */
-#define TOD_TRIG_0 BIT(0)
-#define TOD_TRIG_1 BIT(1)
-#define TOD_TRIG_2 BIT(2)
-#define TOD_TRIG_3 BIT(3)
-
-/* Bit definitions for the GPIO_DPLL_INDICATOR register */
-#define IND_DPLL_INDEX_SHIFT (0)
-#define IND_DPLL_INDEX_MASK (0x7)
-
-/* Bit definitions for the GPIO_LOS_INDICATOR register */
-#define REFMON_INDEX_SHIFT (0)
-#define REFMON_INDEX_MASK (0xf)
-/* Active level of LOS indicator, 0=low 1=high */
-#define ACTIVE_LEVEL BIT(4)
-
-/* Bit definitions for the GPIO_REF_INPUT_DSQ_0 register */
-#define DSQ_INP_0 BIT(0)
-#define DSQ_INP_1 BIT(1)
-#define DSQ_INP_2 BIT(2)
-#define DSQ_INP_3 BIT(3)
-#define DSQ_INP_4 BIT(4)
-#define DSQ_INP_5 BIT(5)
-#define DSQ_INP_6 BIT(6)
-#define DSQ_INP_7 BIT(7)
-
-/* Bit definitions for the GPIO_REF_INPUT_DSQ_1 register */
-#define DSQ_INP_8 BIT(0)
-#define DSQ_INP_9 BIT(1)
-#define DSQ_INP_10 BIT(2)
-#define DSQ_INP_11 BIT(3)
-#define DSQ_INP_12 BIT(4)
-#define DSQ_INP_13 BIT(5)
-#define DSQ_INP_14 BIT(6)
-#define DSQ_INP_15 BIT(7)
-
-/* Bit definitions for the GPIO_REF_INPUT_DSQ_2 register */
-#define DSQ_DPLL_0 BIT(0)
-#define DSQ_DPLL_1 BIT(1)
-#define DSQ_DPLL_2 BIT(2)
-#define DSQ_DPLL_3 BIT(3)
-#define DSQ_DPLL_4 BIT(4)
-#define DSQ_DPLL_5 BIT(5)
-#define DSQ_DPLL_6 BIT(6)
-#define DSQ_DPLL_7 BIT(7)
-
-/* Bit definitions for the GPIO_REF_INPUT_DSQ_3 register */
-#define DSQ_DPLL_SYS BIT(0)
-#define GPIO_DSQ_LEVEL BIT(1)
-
-/* Bit definitions for the GPIO_TOD_NOTIFICATION_CFG register */
-#define DPLL_TOD_SHIFT (0)
-#define DPLL_TOD_MASK (0x3)
-#define TOD_READ_SECONDARY BIT(2)
-#define GPIO_ASSERT_LEVEL BIT(3)
-
-/* Bit definitions for the GPIO_CTRL register */
-#define GPIO_FUNCTION_EN BIT(0)
-#define GPIO_CMOS_OD_MODE BIT(1)
-#define GPIO_CONTROL_DIR BIT(2)
-#define GPIO_PU_PD_MODE BIT(3)
-#define GPIO_FUNCTION_SHIFT (4)
-#define GPIO_FUNCTION_MASK (0xf)
-
-/* Bit definitions for the OUT_CTRL_1 register */
-#define OUT_SYNC_DISABLE BIT(7)
-#define SQUELCH_VALUE BIT(6)
-#define SQUELCH_DISABLE BIT(5)
-#define PAD_VDDO_SHIFT (2)
-#define PAD_VDDO_MASK (0x7)
-#define PAD_CMOSDRV_SHIFT (0)
-#define PAD_CMOSDRV_MASK (0x3)
-
-/* Bit definitions for the TOD_CFG register */
-#define TOD_EVEN_PPS_MODE BIT(2)
-#define TOD_OUT_SYNC_ENABLE BIT(1)
-#define TOD_ENABLE BIT(0)
-
-/* Bit definitions for the TOD_WRITE_SELECT_CFG_0 register */
-#define WR_PWM_DECODER_INDEX_SHIFT (4)
-#define WR_PWM_DECODER_INDEX_MASK (0xf)
-#define WR_REF_INDEX_SHIFT (0)
-#define WR_REF_INDEX_MASK (0xf)
-
-/* Bit definitions for the TOD_WRITE_CMD register */
-#define TOD_WRITE_SELECTION_SHIFT (0)
-#define TOD_WRITE_SELECTION_MASK (0xf)
-
-/* Bit definitions for the TOD_READ_PRIMARY_SEL_CFG_0 register */
-#define RD_PWM_DECODER_INDEX_SHIFT (4)
-#define RD_PWM_DECODER_INDEX_MASK (0xf)
-#define RD_REF_INDEX_SHIFT (0)
-#define RD_REF_INDEX_MASK (0xf)
-
-/* Bit definitions for the TOD_READ_PRIMARY_CMD register */
-#define TOD_READ_TRIGGER_MODE BIT(4)
-#define TOD_READ_TRIGGER_SHIFT (0)
-#define TOD_READ_TRIGGER_MASK (0xf)
-
-#endif
diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c
index 9d72ab593f13..af3bc65c4595 100644
--- a/drivers/ptp/ptp_chardev.c
+++ b/drivers/ptp/ptp_chardev.c
@@ -136,6 +136,7 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
caps.pps = ptp->info->pps;
caps.n_pins = ptp->info->n_pins;
caps.cross_timestamping = ptp->info->getcrosststamp != NULL;
+ caps.adjust_phase = ptp->info->adjphase != NULL;
if (copy_to_user((void __user *)arg, &caps, sizeof(caps)))
err = -EFAULT;
break;
@@ -175,7 +176,10 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
}
req.type = PTP_CLK_REQ_EXTTS;
enable = req.extts.flags & PTP_ENABLE_FEATURE ? 1 : 0;
+ if (mutex_lock_interruptible(&ptp->pincfg_mux))
+ return -ERESTARTSYS;
err = ops->enable(ops, &req, enable);
+ mutex_unlock(&ptp->pincfg_mux);
break;
case PTP_PEROUT_REQUEST:
@@ -187,12 +191,46 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
err = -EFAULT;
break;
}
- if (((req.perout.flags & ~PTP_PEROUT_VALID_FLAGS) ||
- req.perout.rsv[0] || req.perout.rsv[1] ||
- req.perout.rsv[2] || req.perout.rsv[3]) &&
- cmd == PTP_PEROUT_REQUEST2) {
- err = -EINVAL;
- break;
+ if (cmd == PTP_PEROUT_REQUEST2) {
+ struct ptp_perout_request *perout = &req.perout;
+
+ if (perout->flags & ~PTP_PEROUT_VALID_FLAGS) {
+ err = -EINVAL;
+ break;
+ }
+ /*
+ * The "on" field has undefined meaning if
+ * PTP_PEROUT_DUTY_CYCLE isn't set, we must still treat
+ * it as reserved, which must be set to zero.
+ */
+ if (!(perout->flags & PTP_PEROUT_DUTY_CYCLE) &&
+ (perout->rsv[0] || perout->rsv[1] ||
+ perout->rsv[2] || perout->rsv[3])) {
+ err = -EINVAL;
+ break;
+ }
+ if (perout->flags & PTP_PEROUT_DUTY_CYCLE) {
+ /* The duty cycle must be subunitary. */
+ if (perout->on.sec > perout->period.sec ||
+ (perout->on.sec == perout->period.sec &&
+ perout->on.nsec > perout->period.nsec)) {
+ err = -ERANGE;
+ break;
+ }
+ }
+ if (perout->flags & PTP_PEROUT_PHASE) {
+ /*
+ * The phase should be specified modulo the
+ * period, therefore anything equal or larger
+ * than 1 period is invalid.
+ */
+ if (perout->phase.sec > perout->period.sec ||
+ (perout->phase.sec == perout->period.sec &&
+ perout->phase.nsec >= perout->period.nsec)) {
+ err = -ERANGE;
+ break;
+ }
+ }
} else if (cmd == PTP_PEROUT_REQUEST) {
req.perout.flags &= PTP_PEROUT_V1_VALID_FLAGS;
req.perout.rsv[0] = 0;
@@ -206,7 +244,10 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
}
req.type = PTP_CLK_REQ_PEROUT;
enable = req.perout.period.sec || req.perout.period.nsec;
+ if (mutex_lock_interruptible(&ptp->pincfg_mux))
+ return -ERESTARTSYS;
err = ops->enable(ops, &req, enable);
+ mutex_unlock(&ptp->pincfg_mux);
break;
case PTP_ENABLE_PPS:
@@ -217,7 +258,10 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
return -EPERM;
req.type = PTP_CLK_REQ_PPS;
enable = arg ? 1 : 0;
+ if (mutex_lock_interruptible(&ptp->pincfg_mux))
+ return -ERESTARTSYS;
err = ops->enable(ops, &req, enable);
+ mutex_unlock(&ptp->pincfg_mux);
break;
case PTP_SYS_OFFSET_PRECISE:
diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
index ac1f2bf9e888..51cae72bb6db 100644
--- a/drivers/ptp/ptp_clock.c
+++ b/drivers/ptp/ptp_clock.c
@@ -24,10 +24,11 @@
#define PTP_PPS_EVENT PPS_CAPTUREASSERT
#define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)
+struct class *ptp_class;
+
/* private globals */
static dev_t ptp_devt;
-static struct class *ptp_class;
static DEFINE_IDA(ptp_clocks_map);
@@ -63,27 +64,6 @@ static void enqueue_external_timestamp(struct timestamp_event_queue *queue,
spin_unlock_irqrestore(&queue->lock, flags);
}
-s32 scaled_ppm_to_ppb(long ppm)
-{
- /*
- * The 'freq' field in the 'struct timex' is in parts per
- * million, but with a 16 bit binary fractional field.
- *
- * We want to calculate
- *
- * ppb = scaled_ppm * 1000 / 2^16
- *
- * which simplifies to
- *
- * ppb = scaled_ppm * 125 / 2^13
- */
- s64 ppb = 1 + ppm;
- ppb *= 125;
- ppb >>= 13;
- return (s32) ppb;
-}
-EXPORT_SYMBOL(scaled_ppm_to_ppb);
-
/* posix clock implementation */
static int ptp_clock_getres(struct posix_clock *pc, struct timespec64 *tp)
@@ -97,6 +77,11 @@ static int ptp_clock_settime(struct posix_clock *pc, const struct timespec64 *tp
{
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ if (ptp_clock_freerun(ptp)) {
+ pr_err("ptp: physical clock is free running\n");
+ return -EBUSY;
+ }
+
return ptp->info->settime64(ptp->info, tp);
}
@@ -118,6 +103,11 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
struct ptp_clock_info *ops;
int err = -EOPNOTSUPP;
+ if (ptp_clock_freerun(ptp)) {
+ pr_err("ptp: physical clock is free running\n");
+ return -EBUSY;
+ }
+
ops = ptp->info;
if (tx->modes & ADJ_SETOFFSET) {
@@ -138,7 +128,7 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
delta = ktime_to_ns(kt);
err = ops->adjtime(ops, delta);
} else if (tx->modes & ADJ_FREQUENCY) {
- s32 ppb = scaled_ppm_to_ppb(tx->freq);
+ long ppb = scaled_ppm_to_ppb(tx->freq);
if (ppb > ops->max_adj || ppb < -ops->max_adj)
return -ERANGE;
if (ops->adjfine)
@@ -146,6 +136,15 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct __kernel_timex *tx)
else
err = ops->adjfreq(ops, ppb);
ptp->dialed_frequency = tx->freq;
+ } else if (tx->modes & ADJ_OFFSET) {
+ if (ops->adjphase) {
+ s32 offset = tx->offset;
+
+ if (!(tx->modes & ADJ_NANO))
+ offset *= NSEC_PER_USEC;
+
+ err = ops->adjphase(ops, offset);
+ }
} else if (tx->modes == 0) {
tx->freq = ptp->dialed_frequency;
err = 0;
@@ -171,12 +170,22 @@ static void ptp_clock_release(struct device *dev)
struct ptp_clock *ptp = container_of(dev, struct ptp_clock, dev);
ptp_cleanup_pin_groups(ptp);
+ kfree(ptp->vclock_index);
mutex_destroy(&ptp->tsevq_mux);
mutex_destroy(&ptp->pincfg_mux);
- ida_simple_remove(&ptp_clocks_map, ptp->index);
+ mutex_destroy(&ptp->n_vclocks_mux);
+ ida_free(&ptp_clocks_map, ptp->index);
kfree(ptp);
}
+static int ptp_getcycles64(struct ptp_clock_info *info, struct timespec64 *ts)
+{
+ if (info->getcyclesx64)
+ return info->getcyclesx64(info, ts, NULL);
+ else
+ return info->gettime64(info, ts);
+}
+
static void ptp_aux_kworker(struct kthread_work *work)
{
struct ptp_clock *ptp = container_of(work, struct ptp_clock,
@@ -197,6 +206,7 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
{
struct ptp_clock *ptp;
int err = 0, index, major = MAJOR(ptp_devt);
+ size_t size;
if (info->n_alarm > PTP_MAX_ALARMS)
return ERR_PTR(-EINVAL);
@@ -207,7 +217,7 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
if (ptp == NULL)
goto no_memory;
- index = ida_simple_get(&ptp_clocks_map, 0, MINORMASK + 1, GFP_KERNEL);
+ index = ida_alloc_max(&ptp_clocks_map, MINORMASK, GFP_KERNEL);
if (index < 0) {
err = index;
goto no_slot;
@@ -220,8 +230,24 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
spin_lock_init(&ptp->tsevq.lock);
mutex_init(&ptp->tsevq_mux);
mutex_init(&ptp->pincfg_mux);
+ mutex_init(&ptp->n_vclocks_mux);
init_waitqueue_head(&ptp->tsev_wq);
+ if (ptp->info->getcycles64 || ptp->info->getcyclesx64) {
+ ptp->has_cycles = true;
+ if (!ptp->info->getcycles64 && ptp->info->getcyclesx64)
+ ptp->info->getcycles64 = ptp_getcycles64;
+ } else {
+ /* Free running cycle counter not supported, use time. */
+ ptp->info->getcycles64 = ptp_getcycles64;
+
+ if (ptp->info->gettimex64)
+ ptp->info->getcyclesx64 = ptp->info->gettimex64;
+
+ if (ptp->info->getcrosststamp)
+ ptp->info->getcrosscycles = ptp->info->getcrosststamp;
+ }
+
if (ptp->info->do_aux_work) {
kthread_init_delayed_work(&ptp->aux_work, ptp_aux_kworker);
ptp->kworker = kthread_create_worker(0, "ptp%d", ptp->index);
@@ -232,6 +258,22 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
}
}
+ /* PTP virtual clock is being registered under physical clock */
+ if (parent && parent->class && parent->class->name &&
+ strcmp(parent->class->name, "ptp") == 0)
+ ptp->is_virtual_clock = true;
+
+ if (!ptp->is_virtual_clock) {
+ ptp->max_vclocks = PTP_DEFAULT_MAX_VCLOCKS;
+
+ size = sizeof(int) * ptp->max_vclocks;
+ ptp->vclock_index = kzalloc(size, GFP_KERNEL);
+ if (!ptp->vclock_index) {
+ err = -ENOMEM;
+ goto no_mem_for_vclocks;
+ }
+ }
+
err = ptp_populate_pin_groups(ptp);
if (err)
goto no_pin_groups;
@@ -249,6 +291,7 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
pr_err("failed to register pps source\n");
goto no_pps;
}
+ ptp->pps_source->lookup_cookie = ptp;
}
/* Initialize a new device of our class in our clock structure. */
@@ -264,24 +307,32 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
/* Create a posix clock and link it to the device. */
err = posix_clock_register(&ptp->clock, &ptp->dev);
if (err) {
+ if (ptp->pps_source)
+ pps_unregister_source(ptp->pps_source);
+
+ if (ptp->kworker)
+ kthread_destroy_worker(ptp->kworker);
+
+ put_device(&ptp->dev);
+
pr_err("failed to create posix clock\n");
- goto no_clock;
+ return ERR_PTR(err);
}
return ptp;
-no_clock:
- if (ptp->pps_source)
- pps_unregister_source(ptp->pps_source);
no_pps:
ptp_cleanup_pin_groups(ptp);
no_pin_groups:
+ kfree(ptp->vclock_index);
+no_mem_for_vclocks:
if (ptp->kworker)
kthread_destroy_worker(ptp->kworker);
kworker_err:
mutex_destroy(&ptp->tsevq_mux);
mutex_destroy(&ptp->pincfg_mux);
- ida_simple_remove(&ptp_clocks_map, index);
+ mutex_destroy(&ptp->n_vclocks_mux);
+ ida_free(&ptp_clocks_map, index);
no_slot:
kfree(ptp);
no_memory:
@@ -289,8 +340,20 @@ no_memory:
}
EXPORT_SYMBOL(ptp_clock_register);
+static int unregister_vclock(struct device *dev, void *data)
+{
+ struct ptp_clock *ptp = dev_get_drvdata(dev);
+
+ ptp_vclock_unregister(info_to_vclock(ptp->info));
+ return 0;
+}
+
int ptp_clock_unregister(struct ptp_clock *ptp)
{
+ if (ptp_vclock_in_use(ptp)) {
+ device_for_each_child(&ptp->dev, NULL, unregister_vclock);
+ }
+
ptp->defunct = 1;
wake_up_interruptible(&ptp->tsev_wq);
@@ -348,7 +411,6 @@ int ptp_find_pin(struct ptp_clock *ptp,
struct ptp_pin_desc *pin = NULL;
int i;
- mutex_lock(&ptp->pincfg_mux);
for (i = 0; i < ptp->info->n_pins; i++) {
if (ptp->info->pin_config[i].func == func &&
ptp->info->pin_config[i].chan == chan) {
@@ -356,12 +418,26 @@ int ptp_find_pin(struct ptp_clock *ptp,
break;
}
}
- mutex_unlock(&ptp->pincfg_mux);
return pin ? i : -1;
}
EXPORT_SYMBOL(ptp_find_pin);
+int ptp_find_pin_unlocked(struct ptp_clock *ptp,
+ enum ptp_pin_function func, unsigned int chan)
+{
+ int result;
+
+ mutex_lock(&ptp->pincfg_mux);
+
+ result = ptp_find_pin(ptp, func, chan);
+
+ mutex_unlock(&ptp->pincfg_mux);
+
+ return result;
+}
+EXPORT_SYMBOL(ptp_find_pin_unlocked);
+
int ptp_schedule_worker(struct ptp_clock *ptp, unsigned long delay)
{
return kthread_mod_delayed_work(ptp->kworker, &ptp->aux_work, delay);
diff --git a/drivers/ptp/ptp_clockmatrix.c b/drivers/ptp/ptp_clockmatrix.c
index 032e112c3dd9..c9d451bf89e2 100644
--- a/drivers/ptp/ptp_clockmatrix.c
+++ b/drivers/ptp/ptp_clockmatrix.c
@@ -6,12 +6,18 @@
* Copyright (C) 2019 Integrated Device Technology, Inc., a Renesas Company.
*/
#include <linux/firmware.h>
-#include <linux/i2c.h>
+#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/delay.h>
+#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/timekeeping.h>
+#include <linux/string.h>
+#include <linux/of.h>
+#include <linux/mfd/rsmu.h>
+#include <linux/mfd/idt8a340_reg.h>
+#include <asm/unaligned.h>
#include "ptp_private.h"
#include "ptp_clockmatrix.h"
@@ -22,7 +28,78 @@ MODULE_AUTHOR("IDT support-1588 <IDT-support-1588@lm.renesas.com>");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL");
+/*
+ * The name of the firmware file to be loaded
+ * over-rides any automatic selection
+ */
+static char *firmware;
+module_param(firmware, charp, 0);
+
#define SETTIME_CORRECTION (0)
+#define EXTTS_PERIOD_MS (95)
+
+static int _idtcm_adjfine(struct idtcm_channel *channel, long scaled_ppm);
+
+static inline int idtcm_read(struct idtcm *idtcm,
+ u16 module,
+ u16 regaddr,
+ u8 *buf,
+ u16 count)
+{
+ return regmap_bulk_read(idtcm->regmap, module + regaddr, buf, count);
+}
+
+static inline int idtcm_write(struct idtcm *idtcm,
+ u16 module,
+ u16 regaddr,
+ u8 *buf,
+ u16 count)
+{
+ return regmap_bulk_write(idtcm->regmap, module + regaddr, buf, count);
+}
+
+static int contains_full_configuration(struct idtcm *idtcm,
+ const struct firmware *fw)
+{
+ struct idtcm_fwrc *rec = (struct idtcm_fwrc *)fw->data;
+ u16 scratch = IDTCM_FW_REG(idtcm->fw_ver, V520, SCRATCH);
+ s32 full_count;
+ s32 count = 0;
+ u16 regaddr;
+ u8 loaddr;
+ s32 len;
+
+ /* 4 bytes skipped every 0x80 */
+ full_count = (scratch - GPIO_USER_CONTROL) -
+ ((scratch >> 7) - (GPIO_USER_CONTROL >> 7)) * 4;
+
+ /* If the firmware contains 'full configuration' SM_RESET can be used
+ * to ensure proper configuration.
+ *
+ * Full configuration is defined as the number of programmable
+ * bytes within the configuration range minus page offset addr range.
+ */
+ for (len = fw->size; len > 0; len -= sizeof(*rec)) {
+ regaddr = rec->hiaddr << 8;
+ regaddr |= rec->loaddr;
+
+ loaddr = rec->loaddr;
+
+ rec++;
+
+ /* Top (status registers) and bottom are read-only */
+ if (regaddr < GPIO_USER_CONTROL || regaddr >= scratch)
+ continue;
+
+ /* Page size 128, last 4 bytes of page skipped */
+ if ((loaddr > 0x7b && loaddr <= 0x7f) || loaddr > 0xfb)
+ continue;
+
+ count++;
+ }
+
+ return (count >= full_count);
+}
static int char_array_to_timespec(u8 *buf,
u8 count,
@@ -84,133 +161,291 @@ static int timespec_to_char_array(struct timespec64 const *ts,
return 0;
}
-static int idtcm_xfer(struct idtcm *idtcm,
- u8 regaddr,
- u8 *buf,
- u16 count,
- bool write)
+static int idtcm_strverscmp(const char *version1, const char *version2)
{
- struct i2c_client *client = idtcm->client;
- struct i2c_msg msg[2];
- int cnt;
-
- msg[0].addr = client->addr;
- msg[0].flags = 0;
- msg[0].len = 1;
- msg[0].buf = &regaddr;
-
- msg[1].addr = client->addr;
- msg[1].flags = write ? 0 : I2C_M_RD;
- msg[1].len = count;
- msg[1].buf = buf;
+ u8 ver1[3], ver2[3];
+ int i;
- cnt = i2c_transfer(client->adapter, msg, 2);
+ if (sscanf(version1, "%hhu.%hhu.%hhu",
+ &ver1[0], &ver1[1], &ver1[2]) != 3)
+ return -1;
+ if (sscanf(version2, "%hhu.%hhu.%hhu",
+ &ver2[0], &ver2[1], &ver2[2]) != 3)
+ return -1;
- if (cnt < 0) {
- dev_err(&client->dev, "i2c_transfer returned %d\n", cnt);
- return cnt;
- } else if (cnt != 2) {
- dev_err(&client->dev,
- "i2c_transfer sent only %d of %d messages\n", cnt, 2);
- return -EIO;
+ for (i = 0; i < 3; i++) {
+ if (ver1[i] > ver2[i])
+ return 1;
+ if (ver1[i] < ver2[i])
+ return -1;
}
return 0;
}
-static int idtcm_page_offset(struct idtcm *idtcm, u8 val)
+static enum fw_version idtcm_fw_version(const char *version)
{
- u8 buf[4];
- int err;
+ enum fw_version ver = V_DEFAULT;
- if (idtcm->page_offset == val)
- return 0;
+ if (idtcm_strverscmp(version, "4.8.7") >= 0)
+ ver = V487;
- buf[0] = 0x0;
- buf[1] = val;
- buf[2] = 0x10;
- buf[3] = 0x20;
+ if (idtcm_strverscmp(version, "5.2.0") >= 0)
+ ver = V520;
- err = idtcm_xfer(idtcm, PAGE_ADDR, buf, sizeof(buf), 1);
+ return ver;
+}
- if (err)
- dev_err(&idtcm->client->dev, "failed to set page offset\n");
- else
- idtcm->page_offset = val;
+static int clear_boot_status(struct idtcm *idtcm)
+{
+ u8 buf[4] = {0};
+
+ return idtcm_write(idtcm, GENERAL_STATUS, BOOT_STATUS, buf, sizeof(buf));
+}
+
+static int read_boot_status(struct idtcm *idtcm, u32 *status)
+{
+ int err;
+ u8 buf[4] = {0};
+
+ err = idtcm_read(idtcm, GENERAL_STATUS, BOOT_STATUS, buf, sizeof(buf));
+
+ *status = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
return err;
}
-static int _idtcm_rdwr(struct idtcm *idtcm,
- u16 regaddr,
- u8 *buf,
- u16 count,
- bool write)
+static int wait_for_boot_status_ready(struct idtcm *idtcm)
+{
+ u32 status = 0;
+ u8 i = 30; /* 30 * 100ms = 3s */
+ int err;
+
+ do {
+ err = read_boot_status(idtcm, &status);
+ if (err)
+ return err;
+
+ if (status == 0xA0)
+ return 0;
+
+ msleep(100);
+ i--;
+
+ } while (i);
+
+ dev_warn(idtcm->dev, "%s timed out", __func__);
+
+ return -EBUSY;
+}
+
+static int arm_tod_read_trig_sel_refclk(struct idtcm_channel *channel, u8 ref)
{
- u8 hi;
- u8 lo;
+ struct idtcm *idtcm = channel->idtcm;
+ u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_SECONDARY_CMD);
+ u8 val = 0;
int err;
- hi = (regaddr >> 8) & 0xff;
- lo = regaddr & 0xff;
+ val &= ~(WR_REF_INDEX_MASK << WR_REF_INDEX_SHIFT);
+ val |= (ref << WR_REF_INDEX_SHIFT);
- err = idtcm_page_offset(idtcm, hi);
+ err = idtcm_write(idtcm, channel->tod_read_secondary,
+ TOD_READ_SECONDARY_SEL_CFG_0, &val, sizeof(val));
+ if (err)
+ return err;
+
+ val = 0 | (SCSR_TOD_READ_TRIG_SEL_REFCLK << TOD_READ_TRIGGER_SHIFT);
+ err = idtcm_write(idtcm, channel->tod_read_secondary, tod_read_cmd,
+ &val, sizeof(val));
if (err)
- goto out;
+ dev_err(idtcm->dev, "%s: err = %d", __func__, err);
- err = idtcm_xfer(idtcm, lo, buf, count, write);
-out:
return err;
}
-static int idtcm_read(struct idtcm *idtcm,
- u16 module,
- u16 regaddr,
- u8 *buf,
- u16 count)
+static bool is_single_shot(u8 mask)
{
- return _idtcm_rdwr(idtcm, module + regaddr, buf, count, false);
+ /* Treat single bit ToD masks as continuous trigger */
+ return !(mask <= 8 && is_power_of_2(mask));
}
-static int idtcm_write(struct idtcm *idtcm,
- u16 module,
- u16 regaddr,
- u8 *buf,
- u16 count)
+static int idtcm_extts_enable(struct idtcm_channel *channel,
+ struct ptp_clock_request *rq, int on)
{
- return _idtcm_rdwr(idtcm, module + regaddr, buf, count, true);
+ u8 index = rq->extts.index;
+ struct idtcm *idtcm;
+ u8 mask = 1 << index;
+ int err = 0;
+ u8 old_mask;
+ int ref;
+
+ idtcm = channel->idtcm;
+ old_mask = idtcm->extts_mask;
+
+ /* Reject requests with unsupported flags */
+ if (rq->extts.flags & ~(PTP_ENABLE_FEATURE |
+ PTP_RISING_EDGE |
+ PTP_FALLING_EDGE |
+ PTP_STRICT_FLAGS))
+ return -EOPNOTSUPP;
+
+ /* Reject requests to enable time stamping on falling edge */
+ if ((rq->extts.flags & PTP_ENABLE_FEATURE) &&
+ (rq->extts.flags & PTP_FALLING_EDGE))
+ return -EOPNOTSUPP;
+
+ if (index >= MAX_TOD)
+ return -EINVAL;
+
+ if (on) {
+ /* Support triggering more than one TOD_0/1/2/3 by same pin */
+ /* Use the pin configured for the channel */
+ ref = ptp_find_pin(channel->ptp_clock, PTP_PF_EXTTS, channel->tod);
+
+ if (ref < 0) {
+ dev_err(idtcm->dev, "%s: No valid pin found for TOD%d!\n",
+ __func__, channel->tod);
+ return -EBUSY;
+ }
+
+ err = arm_tod_read_trig_sel_refclk(&idtcm->channel[index], ref);
+
+ if (err == 0) {
+ idtcm->extts_mask |= mask;
+ idtcm->event_channel[index] = channel;
+ idtcm->channel[index].refn = ref;
+ idtcm->extts_single_shot = is_single_shot(idtcm->extts_mask);
+
+ if (old_mask)
+ return 0;
+
+ schedule_delayed_work(&idtcm->extts_work,
+ msecs_to_jiffies(EXTTS_PERIOD_MS));
+ }
+ } else {
+ idtcm->extts_mask &= ~mask;
+ idtcm->extts_single_shot = is_single_shot(idtcm->extts_mask);
+
+ if (idtcm->extts_mask == 0)
+ cancel_delayed_work(&idtcm->extts_work);
+ }
+
+ return err;
}
-static int _idtcm_gettime(struct idtcm_channel *channel,
- struct timespec64 *ts)
+static int read_sys_apll_status(struct idtcm *idtcm, u8 *status)
+{
+ return idtcm_read(idtcm, STATUS, DPLL_SYS_APLL_STATUS, status,
+ sizeof(u8));
+}
+
+static int read_sys_dpll_status(struct idtcm *idtcm, u8 *status)
+{
+ return idtcm_read(idtcm, STATUS, DPLL_SYS_STATUS, status, sizeof(u8));
+}
+
+static int wait_for_sys_apll_dpll_lock(struct idtcm *idtcm)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(LOCK_TIMEOUT_MS);
+ u8 apll = 0;
+ u8 dpll = 0;
+ int err;
+
+ do {
+ err = read_sys_apll_status(idtcm, &apll);
+ if (err)
+ return err;
+
+ err = read_sys_dpll_status(idtcm, &dpll);
+ if (err)
+ return err;
+
+ apll &= SYS_APLL_LOSS_LOCK_LIVE_MASK;
+ dpll &= DPLL_SYS_STATE_MASK;
+
+ if (apll == SYS_APLL_LOSS_LOCK_LIVE_LOCKED &&
+ dpll == DPLL_STATE_LOCKED) {
+ return 0;
+ } else if (dpll == DPLL_STATE_FREERUN ||
+ dpll == DPLL_STATE_HOLDOVER ||
+ dpll == DPLL_STATE_OPEN_LOOP) {
+ dev_warn(idtcm->dev,
+ "No wait state: DPLL_SYS_STATE %d", dpll);
+ return -EPERM;
+ }
+
+ msleep(LOCK_POLL_INTERVAL_MS);
+ } while (time_is_after_jiffies(timeout));
+
+ dev_warn(idtcm->dev,
+ "%d ms lock timeout: SYS APLL Loss Lock %d SYS DPLL state %d",
+ LOCK_TIMEOUT_MS, apll, dpll);
+
+ return -ETIME;
+}
+
+static void wait_for_chip_ready(struct idtcm *idtcm)
+{
+ if (wait_for_boot_status_ready(idtcm))
+ dev_warn(idtcm->dev, "BOOT_STATUS != 0xA0");
+
+ if (wait_for_sys_apll_dpll_lock(idtcm))
+ dev_warn(idtcm->dev,
+ "Continuing while SYS APLL/DPLL is not locked");
+}
+
+static int _idtcm_gettime_triggered(struct idtcm_channel *channel,
+ struct timespec64 *ts)
{
struct idtcm *idtcm = channel->idtcm;
+ u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_SECONDARY_CMD);
u8 buf[TOD_BYTE_COUNT];
u8 trigger;
int err;
- err = idtcm_read(idtcm, channel->tod_read_primary,
- TOD_READ_PRIMARY_CMD, &trigger, sizeof(trigger));
+ err = idtcm_read(idtcm, channel->tod_read_secondary,
+ tod_read_cmd, &trigger, sizeof(trigger));
if (err)
return err;
- trigger &= ~(TOD_READ_TRIGGER_MASK << TOD_READ_TRIGGER_SHIFT);
- trigger |= (1 << TOD_READ_TRIGGER_SHIFT);
- trigger |= TOD_READ_TRIGGER_MODE;
-
- err = idtcm_write(idtcm, channel->tod_read_primary,
- TOD_READ_PRIMARY_CMD, &trigger, sizeof(trigger));
+ if (trigger & TOD_READ_TRIGGER_MASK)
+ return -EBUSY;
+ err = idtcm_read(idtcm, channel->tod_read_secondary,
+ TOD_READ_SECONDARY_BASE, buf, sizeof(buf));
if (err)
return err;
- if (idtcm->calculate_overhead_flag)
- idtcm->start_time = ktime_get_raw();
+ return char_array_to_timespec(buf, sizeof(buf), ts);
+}
- err = idtcm_read(idtcm, channel->tod_read_primary,
- TOD_READ_PRIMARY, buf, sizeof(buf));
+static int _idtcm_gettime(struct idtcm_channel *channel,
+ struct timespec64 *ts, u8 timeout)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_PRIMARY_CMD);
+ u8 buf[TOD_BYTE_COUNT];
+ u8 trigger;
+ int err;
+
+ /* wait trigger to be 0 */
+ do {
+ if (timeout-- == 0)
+ return -EIO;
+ if (idtcm->calculate_overhead_flag)
+ idtcm->start_time = ktime_get_raw();
+
+ err = idtcm_read(idtcm, channel->tod_read_primary,
+ tod_read_cmd, &trigger,
+ sizeof(trigger));
+ if (err)
+ return err;
+ } while (trigger & TOD_READ_TRIGGER_MASK);
+
+ err = idtcm_read(idtcm, channel->tod_read_primary,
+ TOD_READ_PRIMARY_BASE, buf, sizeof(buf));
if (err)
return err;
@@ -219,6 +454,50 @@ static int _idtcm_gettime(struct idtcm_channel *channel,
return err;
}
+static int idtcm_extts_check_channel(struct idtcm *idtcm, u8 todn)
+{
+ struct idtcm_channel *ptp_channel, *extts_channel;
+ struct ptp_clock_event event;
+ struct timespec64 ts;
+ u32 dco_delay = 0;
+ int err;
+
+ extts_channel = &idtcm->channel[todn];
+ ptp_channel = idtcm->event_channel[todn];
+
+ if (extts_channel == ptp_channel)
+ dco_delay = ptp_channel->dco_delay;
+
+ err = _idtcm_gettime_triggered(extts_channel, &ts);
+ if (err)
+ return err;
+
+ /* Triggered - save timestamp */
+ event.type = PTP_CLOCK_EXTTS;
+ event.index = todn;
+ event.timestamp = timespec64_to_ns(&ts) - dco_delay;
+ ptp_clock_event(ptp_channel->ptp_clock, &event);
+
+ return err;
+}
+
+static int _idtcm_gettime_immediate(struct idtcm_channel *channel,
+ struct timespec64 *ts)
+{
+ struct idtcm *idtcm = channel->idtcm;
+
+ u16 tod_read_cmd = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_READ_PRIMARY_CMD);
+ u8 val = (SCSR_TOD_READ_TRIG_SEL_IMMEDIATE << TOD_READ_TRIGGER_SHIFT);
+ int err;
+
+ err = idtcm_write(idtcm, channel->tod_read_primary,
+ tod_read_cmd, &val, sizeof(val));
+ if (err)
+ return err;
+
+ return _idtcm_gettime(channel, ts, 10);
+}
+
static int _sync_pll_output(struct idtcm *idtcm,
u8 pll,
u8 sync_src,
@@ -229,8 +508,9 @@ static int _sync_pll_output(struct idtcm *idtcm,
u8 val;
u16 sync_ctrl0;
u16 sync_ctrl1;
+ u8 temp;
- if ((qn == 0) && (qn_plus_1 == 0))
+ if (qn == 0 && qn_plus_1 == 0)
return 0;
switch (pll) {
@@ -294,6 +574,50 @@ static int _sync_pll_output(struct idtcm *idtcm,
if (err)
return err;
+ /* PLL5 can have OUT8 as second additional output. */
+ if (pll == 5 && qn_plus_1 != 0) {
+ err = idtcm_read(idtcm, 0, HW_Q8_CTRL_SPARE,
+ &temp, sizeof(temp));
+ if (err)
+ return err;
+
+ temp &= ~(Q9_TO_Q8_SYNC_TRIG);
+
+ err = idtcm_write(idtcm, 0, HW_Q8_CTRL_SPARE,
+ &temp, sizeof(temp));
+ if (err)
+ return err;
+
+ temp |= Q9_TO_Q8_SYNC_TRIG;
+
+ err = idtcm_write(idtcm, 0, HW_Q8_CTRL_SPARE,
+ &temp, sizeof(temp));
+ if (err)
+ return err;
+ }
+
+ /* PLL6 can have OUT11 as second additional output. */
+ if (pll == 6 && qn_plus_1 != 0) {
+ err = idtcm_read(idtcm, 0, HW_Q11_CTRL_SPARE,
+ &temp, sizeof(temp));
+ if (err)
+ return err;
+
+ temp &= ~(Q10_TO_Q11_SYNC_TRIG);
+
+ err = idtcm_write(idtcm, 0, HW_Q11_CTRL_SPARE,
+ &temp, sizeof(temp));
+ if (err)
+ return err;
+
+ temp |= Q10_TO_Q11_SYNC_TRIG;
+
+ err = idtcm_write(idtcm, 0, HW_Q11_CTRL_SPARE,
+ &temp, sizeof(temp));
+ if (err)
+ return err;
+ }
+
/* Place master sync out of reset */
val &= ~(SYNCTRL1_MASTER_SYNC_RST);
err = idtcm_write(idtcm, 0, sync_ctrl1, &val, sizeof(val));
@@ -304,48 +628,72 @@ static int _sync_pll_output(struct idtcm *idtcm,
static int idtcm_sync_pps_output(struct idtcm_channel *channel)
{
struct idtcm *idtcm = channel->idtcm;
-
u8 pll;
- u8 sync_src;
u8 qn;
u8 qn_plus_1;
int err = 0;
-
+ u8 out8_mux = 0;
+ u8 out11_mux = 0;
+ u8 temp;
u16 output_mask = channel->output_mask;
- switch (channel->dpll_n) {
- case DPLL_0:
- sync_src = SYNC_SOURCE_DPLL0_TOD_PPS;
- break;
- case DPLL_1:
- sync_src = SYNC_SOURCE_DPLL1_TOD_PPS;
- break;
- case DPLL_2:
- sync_src = SYNC_SOURCE_DPLL2_TOD_PPS;
- break;
- case DPLL_3:
- sync_src = SYNC_SOURCE_DPLL3_TOD_PPS;
- break;
- default:
- return -EINVAL;
- }
+ err = idtcm_read(idtcm, 0, HW_Q8_CTRL_SPARE,
+ &temp, sizeof(temp));
+ if (err)
+ return err;
- for (pll = 0; pll < 8; pll++) {
+ if ((temp & Q9_TO_Q8_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) ==
+ Q9_TO_Q8_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK)
+ out8_mux = 1;
+
+ err = idtcm_read(idtcm, 0, HW_Q11_CTRL_SPARE,
+ &temp, sizeof(temp));
+ if (err)
+ return err;
+
+ if ((temp & Q10_TO_Q11_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK) ==
+ Q10_TO_Q11_FANOUT_AND_CLOCK_SYNC_ENABLE_MASK)
+ out11_mux = 1;
- qn = output_mask & 0x1;
- output_mask = output_mask >> 1;
+ for (pll = 0; pll < 8; pll++) {
+ qn = 0;
+ qn_plus_1 = 0;
if (pll < 4) {
/* First 4 pll has 2 outputs */
+ qn = output_mask & 0x1;
+ output_mask = output_mask >> 1;
qn_plus_1 = output_mask & 0x1;
output_mask = output_mask >> 1;
- } else {
- qn_plus_1 = 0;
+ } else if (pll == 4) {
+ if (out8_mux == 0) {
+ qn = output_mask & 0x1;
+ output_mask = output_mask >> 1;
+ }
+ } else if (pll == 5) {
+ if (out8_mux) {
+ qn_plus_1 = output_mask & 0x1;
+ output_mask = output_mask >> 1;
+ }
+ qn = output_mask & 0x1;
+ output_mask = output_mask >> 1;
+ } else if (pll == 6) {
+ qn = output_mask & 0x1;
+ output_mask = output_mask >> 1;
+ if (out11_mux) {
+ qn_plus_1 = output_mask & 0x1;
+ output_mask = output_mask >> 1;
+ }
+ } else if (pll == 7) {
+ if (out11_mux == 0) {
+ qn = output_mask & 0x1;
+ output_mask = output_mask >> 1;
+ }
}
- if ((qn != 0) || (qn_plus_1 != 0))
- err = _sync_pll_output(idtcm, pll, sync_src, qn,
- qn_plus_1);
+ if (qn != 0 || qn_plus_1 != 0)
+ err = _sync_pll_output(idtcm, pll, channel->sync_src,
+ qn, qn_plus_1);
if (err)
return err;
@@ -354,12 +702,11 @@ static int idtcm_sync_pps_output(struct idtcm_channel *channel)
return err;
}
-static int _idtcm_set_dpll_tod(struct idtcm_channel *channel,
- struct timespec64 const *ts,
- enum hw_tod_write_trig_sel wr_trig)
+static int _idtcm_set_dpll_hw_tod(struct idtcm_channel *channel,
+ struct timespec64 const *ts,
+ enum hw_tod_write_trig_sel wr_trig)
{
struct idtcm *idtcm = channel->idtcm;
-
u8 buf[TOD_BYTE_COUNT];
u8 cmd;
int err;
@@ -369,7 +716,6 @@ static int _idtcm_set_dpll_tod(struct idtcm_channel *channel,
/* Configure HW TOD write trigger. */
err = idtcm_read(idtcm, channel->hw_dpll_n, HW_DPLL_TOD_CTRL_1,
&cmd, sizeof(cmd));
-
if (err)
return err;
@@ -378,20 +724,16 @@ static int _idtcm_set_dpll_tod(struct idtcm_channel *channel,
err = idtcm_write(idtcm, channel->hw_dpll_n, HW_DPLL_TOD_CTRL_1,
&cmd, sizeof(cmd));
-
if (err)
return err;
if (wr_trig != HW_TOD_WR_TRIG_SEL_MSB) {
-
err = timespec_to_char_array(&local_ts, buf, sizeof(buf));
-
if (err)
return err;
err = idtcm_write(idtcm, channel->hw_dpll_n,
HW_DPLL_TOD_OVR__0, buf, sizeof(buf));
-
if (err)
return err;
}
@@ -403,11 +745,11 @@ static int _idtcm_set_dpll_tod(struct idtcm_channel *channel,
&cmd, sizeof(cmd));
if (wr_trig == HW_TOD_WR_TRIG_SEL_MSB) {
-
if (idtcm->calculate_overhead_flag) {
/* Assumption: I2C @ 400KHz */
- total_overhead_ns = ktime_to_ns(ktime_get_raw()
- - idtcm->start_time)
+ ktime_t diff = ktime_sub(ktime_get_raw(),
+ idtcm->start_time);
+ total_overhead_ns = ktime_to_ns(diff)
+ idtcm->tod_write_overhead_ns
+ SETTIME_CORRECTION;
@@ -417,7 +759,6 @@ static int _idtcm_set_dpll_tod(struct idtcm_channel *channel,
}
err = timespec_to_char_array(&local_ts, buf, sizeof(buf));
-
if (err)
return err;
@@ -428,42 +769,138 @@ static int _idtcm_set_dpll_tod(struct idtcm_channel *channel,
return err;
}
-static int _idtcm_settime(struct idtcm_channel *channel,
- struct timespec64 const *ts,
- enum hw_tod_write_trig_sel wr_trig)
+static int _idtcm_set_dpll_scsr_tod(struct idtcm_channel *channel,
+ struct timespec64 const *ts,
+ enum scsr_tod_write_trig_sel wr_trig,
+ enum scsr_tod_write_type_sel wr_type)
{
struct idtcm *idtcm = channel->idtcm;
- s32 retval;
- int err;
- int i;
- u8 trig_sel;
+ unsigned char buf[TOD_BYTE_COUNT], cmd;
+ struct timespec64 local_ts = *ts;
+ int err, count = 0;
+
+ timespec64_add_ns(&local_ts, SETTIME_CORRECTION);
+
+ err = timespec_to_char_array(&local_ts, buf, sizeof(buf));
+ if (err)
+ return err;
- err = _idtcm_set_dpll_tod(channel, ts, wr_trig);
+ err = idtcm_write(idtcm, channel->tod_write, TOD_WRITE,
+ buf, sizeof(buf));
+ if (err)
+ return err;
+ /* Trigger the write operation. */
+ err = idtcm_read(idtcm, channel->tod_write, TOD_WRITE_CMD,
+ &cmd, sizeof(cmd));
+ if (err)
+ return err;
+
+ cmd &= ~(TOD_WRITE_SELECTION_MASK << TOD_WRITE_SELECTION_SHIFT);
+ cmd &= ~(TOD_WRITE_TYPE_MASK << TOD_WRITE_TYPE_SHIFT);
+ cmd |= (wr_trig << TOD_WRITE_SELECTION_SHIFT);
+ cmd |= (wr_type << TOD_WRITE_TYPE_SHIFT);
+
+ err = idtcm_write(idtcm, channel->tod_write, TOD_WRITE_CMD,
+ &cmd, sizeof(cmd));
if (err)
return err;
/* Wait for the operation to complete. */
- for (i = 0; i < 10000; i++) {
- err = idtcm_read(idtcm, channel->hw_dpll_n,
- HW_DPLL_TOD_CTRL_1, &trig_sel,
- sizeof(trig_sel));
+ while (1) {
+ /* pps trigger takes up to 1 sec to complete */
+ if (wr_trig == SCSR_TOD_WR_TRIG_SEL_TODPPS)
+ msleep(50);
+ err = idtcm_read(idtcm, channel->tod_write, TOD_WRITE_CMD,
+ &cmd, sizeof(cmd));
if (err)
return err;
- if (trig_sel == 0x4a)
+ if ((cmd & TOD_WRITE_SELECTION_MASK) == 0)
break;
- err = 1;
+ if (++count > 20) {
+ dev_err(idtcm->dev,
+ "Timed out waiting for the write counter");
+ return -EIO;
+ }
}
- if (err)
+ return 0;
+}
+
+static int get_output_base_addr(enum fw_version ver, u8 outn)
+{
+ int base;
+
+ switch (outn) {
+ case 0:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_0);
+ break;
+ case 1:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_1);
+ break;
+ case 2:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_2);
+ break;
+ case 3:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_3);
+ break;
+ case 4:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_4);
+ break;
+ case 5:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_5);
+ break;
+ case 6:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_6);
+ break;
+ case 7:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_7);
+ break;
+ case 8:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_8);
+ break;
+ case 9:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_9);
+ break;
+ case 10:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_10);
+ break;
+ case 11:
+ base = IDTCM_FW_REG(ver, V520, OUTPUT_11);
+ break;
+ default:
+ base = -EINVAL;
+ }
+
+ return base;
+}
+
+static int _idtcm_settime_deprecated(struct idtcm_channel *channel,
+ struct timespec64 const *ts)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ err = _idtcm_set_dpll_hw_tod(channel, ts, HW_TOD_WR_TRIG_SEL_MSB);
+ if (err) {
+ dev_err(idtcm->dev,
+ "%s: Set HW ToD failed", __func__);
return err;
+ }
- retval = idtcm_sync_pps_output(channel);
+ return idtcm_sync_pps_output(channel);
+}
- return retval;
+static int _idtcm_settime(struct idtcm_channel *channel,
+ struct timespec64 const *ts,
+ enum scsr_tod_write_type_sel wr_type)
+{
+ return _idtcm_set_dpll_scsr_tod(channel, ts,
+ SCSR_TOD_WR_TRIG_SEL_IMMEDIATE,
+ wr_type);
}
static int idtcm_set_phase_pull_in_offset(struct idtcm_channel *channel,
@@ -472,7 +909,6 @@ static int idtcm_set_phase_pull_in_offset(struct idtcm_channel *channel,
int err;
int i;
struct idtcm *idtcm = channel->idtcm;
-
u8 buf[4];
for (i = 0; i < 4; i++) {
@@ -492,7 +928,6 @@ static int idtcm_set_phase_pull_in_slope_limit(struct idtcm_channel *channel,
int err;
u8 i;
struct idtcm *idtcm = channel->idtcm;
-
u8 buf[3];
if (max_ffo_ppb & 0xff000000)
@@ -513,12 +948,10 @@ static int idtcm_start_phase_pull_in(struct idtcm_channel *channel)
{
int err;
struct idtcm *idtcm = channel->idtcm;
-
u8 buf;
err = idtcm_read(idtcm, channel->dpll_phase_pull_in, PULL_IN_CTRL,
&buf, sizeof(buf));
-
if (err)
return err;
@@ -533,19 +966,17 @@ static int idtcm_start_phase_pull_in(struct idtcm_channel *channel)
return err;
}
-static int idtcm_do_phase_pull_in(struct idtcm_channel *channel,
- s32 offset_ns,
- u32 max_ffo_ppb)
+static int do_phase_pull_in_fw(struct idtcm_channel *channel,
+ s32 offset_ns,
+ u32 max_ffo_ppb)
{
int err;
err = idtcm_set_phase_pull_in_offset(channel, -offset_ns);
-
if (err)
return err;
err = idtcm_set_phase_pull_in_slope_limit(channel, max_ffo_ppb);
-
if (err)
return err;
@@ -554,20 +985,67 @@ static int idtcm_do_phase_pull_in(struct idtcm_channel *channel,
return err;
}
-static int _idtcm_adjtime(struct idtcm_channel *channel, s64 delta)
+static int set_tod_write_overhead(struct idtcm_channel *channel)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ s64 current_ns = 0;
+ s64 lowest_ns = 0;
+ int err;
+ u8 i;
+ ktime_t start;
+ ktime_t stop;
+ ktime_t diff;
+
+ char buf[TOD_BYTE_COUNT] = {0};
+
+ /* Set page offset */
+ idtcm_write(idtcm, channel->hw_dpll_n, HW_DPLL_TOD_OVR__0,
+ buf, sizeof(buf));
+
+ for (i = 0; i < TOD_WRITE_OVERHEAD_COUNT_MAX; i++) {
+ start = ktime_get_raw();
+
+ err = idtcm_write(idtcm, channel->hw_dpll_n,
+ HW_DPLL_TOD_OVR__0, buf, sizeof(buf));
+ if (err)
+ return err;
+
+ stop = ktime_get_raw();
+
+ diff = ktime_sub(stop, start);
+
+ current_ns = ktime_to_ns(diff);
+
+ if (i == 0) {
+ lowest_ns = current_ns;
+ } else {
+ if (current_ns < lowest_ns)
+ lowest_ns = current_ns;
+ }
+ }
+
+ idtcm->tod_write_overhead_ns = lowest_ns;
+
+ return err;
+}
+
+static int _idtcm_adjtime_deprecated(struct idtcm_channel *channel, s64 delta)
{
int err;
struct idtcm *idtcm = channel->idtcm;
struct timespec64 ts;
s64 now;
- if (abs(delta) < PHASE_PULL_IN_THRESHOLD_NS) {
- err = idtcm_do_phase_pull_in(channel, delta, 0);
+ if (abs(delta) < PHASE_PULL_IN_THRESHOLD_NS_DEPRECATED) {
+ err = channel->do_phase_pull_in(channel, delta, 0);
} else {
idtcm->calculate_overhead_flag = 1;
- err = _idtcm_gettime(channel, &ts);
+ err = set_tod_write_overhead(channel);
+ if (err)
+ return err;
+ err = _idtcm_gettime_immediate(channel, &ts);
if (err)
return err;
@@ -576,7 +1054,7 @@ static int _idtcm_adjtime(struct idtcm_channel *channel, s64 delta)
ts = ns_to_timespec64(now);
- err = _idtcm_settime(channel, &ts, HW_TOD_WR_TRIG_SEL_MSB);
+ err = _idtcm_settime_deprecated(channel, &ts);
}
return err;
@@ -584,13 +1062,33 @@ static int _idtcm_adjtime(struct idtcm_channel *channel, s64 delta)
static int idtcm_state_machine_reset(struct idtcm *idtcm)
{
- int err;
u8 byte = SM_RESET_CMD;
+ u32 status = 0;
+ int err;
+ u8 i;
+
+ clear_boot_status(idtcm);
- err = idtcm_write(idtcm, RESET_CTRL, SM_RESET, &byte, sizeof(byte));
+ err = idtcm_write(idtcm, RESET_CTRL,
+ IDTCM_FW_REG(idtcm->fw_ver, V520, SM_RESET),
+ &byte, sizeof(byte));
- if (!err)
- msleep_interruptible(POST_SM_RESET_DELAY_MS);
+ if (!err) {
+ for (i = 0; i < 30; i++) {
+ msleep_interruptible(100);
+ read_boot_status(idtcm, &status);
+
+ if (status == 0xA0) {
+ dev_dbg(idtcm->dev,
+ "SM_RESET completed in %d ms", i * 100);
+ break;
+ }
+ }
+
+ if (!status)
+ dev_err(idtcm->dev,
+ "Timed out waiting for CM_RESET to complete");
+ }
return err;
}
@@ -645,93 +1143,118 @@ static int idtcm_read_otp_scsr_config_select(struct idtcm *idtcm,
config_select, sizeof(u8));
}
-static int process_pll_mask(struct idtcm *idtcm, u32 addr, u8 val, u8 *mask)
-{
- int err = 0;
-
- if (addr == PLL_MASK_ADDR) {
- if ((val & 0xf0) || !(val & 0xf)) {
- dev_err(&idtcm->client->dev,
- "Invalid PLL mask 0x%hhx\n", val);
- err = -EINVAL;
- }
- *mask = val;
- }
-
- return err;
-}
-
static int set_pll_output_mask(struct idtcm *idtcm, u16 addr, u8 val)
{
int err = 0;
switch (addr) {
- case OUTPUT_MASK_PLL0_ADDR:
+ case TOD0_OUT_ALIGN_MASK_ADDR:
SET_U16_LSB(idtcm->channel[0].output_mask, val);
break;
- case OUTPUT_MASK_PLL0_ADDR + 1:
+ case TOD0_OUT_ALIGN_MASK_ADDR + 1:
SET_U16_MSB(idtcm->channel[0].output_mask, val);
break;
- case OUTPUT_MASK_PLL1_ADDR:
+ case TOD1_OUT_ALIGN_MASK_ADDR:
SET_U16_LSB(idtcm->channel[1].output_mask, val);
break;
- case OUTPUT_MASK_PLL1_ADDR + 1:
+ case TOD1_OUT_ALIGN_MASK_ADDR + 1:
SET_U16_MSB(idtcm->channel[1].output_mask, val);
break;
- case OUTPUT_MASK_PLL2_ADDR:
+ case TOD2_OUT_ALIGN_MASK_ADDR:
SET_U16_LSB(idtcm->channel[2].output_mask, val);
break;
- case OUTPUT_MASK_PLL2_ADDR + 1:
+ case TOD2_OUT_ALIGN_MASK_ADDR + 1:
SET_U16_MSB(idtcm->channel[2].output_mask, val);
break;
- case OUTPUT_MASK_PLL3_ADDR:
+ case TOD3_OUT_ALIGN_MASK_ADDR:
SET_U16_LSB(idtcm->channel[3].output_mask, val);
break;
- case OUTPUT_MASK_PLL3_ADDR + 1:
+ case TOD3_OUT_ALIGN_MASK_ADDR + 1:
SET_U16_MSB(idtcm->channel[3].output_mask, val);
break;
default:
- err = -EINVAL;
+ err = -EFAULT; /* Bad address */;
break;
}
return err;
}
+static int set_tod_ptp_pll(struct idtcm *idtcm, u8 index, u8 pll)
+{
+ if (index >= MAX_TOD) {
+ dev_err(idtcm->dev, "ToD%d not supported", index);
+ return -EINVAL;
+ }
+
+ if (pll >= MAX_PLL) {
+ dev_err(idtcm->dev, "Pll%d not supported", pll);
+ return -EINVAL;
+ }
+
+ idtcm->channel[index].pll = pll;
+
+ return 0;
+}
+
static int check_and_set_masks(struct idtcm *idtcm,
u16 regaddr,
u8 val)
{
int err = 0;
- if (set_pll_output_mask(idtcm, regaddr, val)) {
- /* Not an output mask, check for pll mask */
- err = process_pll_mask(idtcm, regaddr, val, &idtcm->pll_mask);
+ switch (regaddr) {
+ case TOD_MASK_ADDR:
+ if ((val & 0xf0) || !(val & 0x0f)) {
+ dev_err(idtcm->dev, "Invalid TOD mask 0x%02x", val);
+ err = -EINVAL;
+ } else {
+ idtcm->tod_mask = val;
+ }
+ break;
+ case TOD0_PTP_PLL_ADDR:
+ err = set_tod_ptp_pll(idtcm, 0, val);
+ break;
+ case TOD1_PTP_PLL_ADDR:
+ err = set_tod_ptp_pll(idtcm, 1, val);
+ break;
+ case TOD2_PTP_PLL_ADDR:
+ err = set_tod_ptp_pll(idtcm, 2, val);
+ break;
+ case TOD3_PTP_PLL_ADDR:
+ err = set_tod_ptp_pll(idtcm, 3, val);
+ break;
+ default:
+ err = set_pll_output_mask(idtcm, regaddr, val);
+ break;
}
return err;
}
-static void display_pll_and_output_masks(struct idtcm *idtcm)
+static void display_pll_and_masks(struct idtcm *idtcm)
{
u8 i;
u8 mask;
- dev_dbg(&idtcm->client->dev, "pllmask = 0x%02x\n", idtcm->pll_mask);
+ dev_dbg(idtcm->dev, "tod_mask = 0x%02x", idtcm->tod_mask);
- for (i = 0; i < MAX_PHC_PLL; i++) {
+ for (i = 0; i < MAX_TOD; i++) {
mask = 1 << i;
- if (mask & idtcm->pll_mask)
- dev_dbg(&idtcm->client->dev,
- "PLL%d output_mask = 0x%04x\n",
- i, idtcm->channel[i].output_mask);
+ if (mask & idtcm->tod_mask)
+ dev_dbg(idtcm->dev,
+ "TOD%d pll = %d output_mask = 0x%04x",
+ i, idtcm->channel[i].pll,
+ idtcm->channel[i].output_mask);
}
}
static int idtcm_load_firmware(struct idtcm *idtcm,
struct device *dev)
{
+ u16 scratch = IDTCM_FW_REG(idtcm->fw_ver, V520, SCRATCH);
+ char fname[128] = FW_FILENAME;
const struct firmware *fw;
struct idtcm_fwrc *rec;
u32 regaddr;
@@ -740,25 +1263,29 @@ static int idtcm_load_firmware(struct idtcm *idtcm,
u8 val;
u8 loaddr;
- dev_dbg(&idtcm->client->dev, "requesting firmware '%s'\n", FW_FILENAME);
+ if (firmware) /* module parameter */
+ snprintf(fname, sizeof(fname), "%s", firmware);
- err = request_firmware(&fw, FW_FILENAME, dev);
+ dev_info(idtcm->dev, "requesting firmware '%s'", fname);
- if (err)
+ err = request_firmware(&fw, fname, dev);
+ if (err) {
+ dev_err(idtcm->dev,
+ "Failed at line %d in %s!", __LINE__, __func__);
return err;
+ }
- dev_dbg(&idtcm->client->dev, "firmware size %zu bytes\n", fw->size);
+ dev_dbg(idtcm->dev, "firmware size %zu bytes", fw->size);
rec = (struct idtcm_fwrc *) fw->data;
- if (fw->size > 0)
+ if (contains_full_configuration(idtcm, fw))
idtcm_state_machine_reset(idtcm);
for (len = fw->size; len > 0; len -= sizeof(*rec)) {
-
if (rec->reserved) {
- dev_err(&idtcm->client->dev,
- "bad firmware, reserved field non-zero\n");
+ dev_err(idtcm->dev,
+ "bad firmware, reserved field non-zero");
err = -EINVAL;
} else {
regaddr = rec->hiaddr << 8;
@@ -772,15 +1299,15 @@ static int idtcm_load_firmware(struct idtcm *idtcm,
err = check_and_set_masks(idtcm, regaddr, val);
}
- if (err == 0) {
+ if (err != -EINVAL) {
+ err = 0;
+
/* Top (status registers) and bottom are read-only */
- if ((regaddr < GPIO_USER_CONTROL)
- || (regaddr >= SCRATCH))
+ if (regaddr < GPIO_USER_CONTROL || regaddr >= scratch)
continue;
/* Page size 128, last 4 bytes of page skipped */
- if (((loaddr > 0x7b) && (loaddr <= 0x7f))
- || ((loaddr > 0xfb) && (loaddr <= 0xff)))
+ if ((loaddr > 0x7b && loaddr <= 0x7f) || loaddr > 0xfb)
continue;
err = idtcm_write(idtcm, regaddr, 0, &val, sizeof(val));
@@ -790,43 +1317,30 @@ static int idtcm_load_firmware(struct idtcm *idtcm,
goto out;
}
- display_pll_and_output_masks(idtcm);
+ display_pll_and_masks(idtcm);
out:
release_firmware(fw);
return err;
}
-static int idtcm_pps_enable(struct idtcm_channel *channel, bool enable)
+static int idtcm_output_enable(struct idtcm_channel *channel,
+ bool enable, unsigned int outn)
{
struct idtcm *idtcm = channel->idtcm;
- u32 module;
- u8 val;
+ int base;
int err;
+ u8 val;
- /*
- * This assumes that the 1-PPS is on the second of the two
- * output. But is this always true?
- */
- switch (channel->dpll_n) {
- case DPLL_0:
- module = OUTPUT_1;
- break;
- case DPLL_1:
- module = OUTPUT_3;
- break;
- case DPLL_2:
- module = OUTPUT_5;
- break;
- case DPLL_3:
- module = OUTPUT_7;
- break;
- default:
- return -EINVAL;
- }
+ base = get_output_base_addr(idtcm->fw_ver, outn);
- err = idtcm_read(idtcm, module, OUT_CTRL_1, &val, sizeof(val));
+ if (!(base > 0)) {
+ dev_err(idtcm->dev,
+ "%s - Unsupported out%d", __func__, outn);
+ return base;
+ }
+ err = idtcm_read(idtcm, (u16)base, OUT_CTRL_1, &val, sizeof(val));
if (err)
return err;
@@ -835,55 +1349,408 @@ static int idtcm_pps_enable(struct idtcm_channel *channel, bool enable)
else
val &= ~SQUELCH_DISABLE;
- err = idtcm_write(idtcm, module, OUT_CTRL_1, &val, sizeof(val));
+ return idtcm_write(idtcm, (u16)base, OUT_CTRL_1, &val, sizeof(val));
+}
+
+static int idtcm_perout_enable(struct idtcm_channel *channel,
+ struct ptp_perout_request *perout,
+ bool enable)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ struct timespec64 ts = {0, 0};
+ int err;
+
+ err = idtcm_output_enable(channel, enable, perout->index);
+ if (err) {
+ dev_err(idtcm->dev, "Unable to set output enable");
+ return err;
+ }
+
+ /* Align output to internal 1 PPS */
+ return _idtcm_settime(channel, &ts, SCSR_TOD_WR_TYPE_SEL_DELTA_PLUS);
+}
+
+static int idtcm_get_pll_mode(struct idtcm_channel *channel,
+ enum pll_mode *mode)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+ u8 dpll_mode;
+
+ err = idtcm_read(idtcm, channel->dpll_n,
+ IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE),
+ &dpll_mode, sizeof(dpll_mode));
if (err)
return err;
+ *mode = (dpll_mode >> PLL_MODE_SHIFT) & PLL_MODE_MASK;
+
return 0;
}
static int idtcm_set_pll_mode(struct idtcm_channel *channel,
- enum pll_mode pll_mode)
+ enum pll_mode mode)
{
struct idtcm *idtcm = channel->idtcm;
int err;
u8 dpll_mode;
- err = idtcm_read(idtcm, channel->dpll_n, DPLL_MODE,
+ err = idtcm_read(idtcm, channel->dpll_n,
+ IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE),
&dpll_mode, sizeof(dpll_mode));
if (err)
return err;
dpll_mode &= ~(PLL_MODE_MASK << PLL_MODE_SHIFT);
- dpll_mode |= (pll_mode << PLL_MODE_SHIFT);
+ dpll_mode |= (mode << PLL_MODE_SHIFT);
- channel->pll_mode = pll_mode;
-
- err = idtcm_write(idtcm, channel->dpll_n, DPLL_MODE,
+ err = idtcm_write(idtcm, channel->dpll_n,
+ IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_MODE),
&dpll_mode, sizeof(dpll_mode));
+ return err;
+}
+
+static int idtcm_get_manual_reference(struct idtcm_channel *channel,
+ enum manual_reference *ref)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ u8 dpll_manu_ref_cfg;
+ int err;
+
+ err = idtcm_read(idtcm, channel->dpll_ctrl_n,
+ DPLL_CTRL_DPLL_MANU_REF_CFG,
+ &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg));
if (err)
return err;
+ dpll_manu_ref_cfg &= (MANUAL_REFERENCE_MASK << MANUAL_REFERENCE_SHIFT);
+
+ *ref = dpll_manu_ref_cfg >> MANUAL_REFERENCE_SHIFT;
+
return 0;
}
+static int idtcm_set_manual_reference(struct idtcm_channel *channel,
+ enum manual_reference ref)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ u8 dpll_manu_ref_cfg;
+ int err;
+
+ err = idtcm_read(idtcm, channel->dpll_ctrl_n,
+ DPLL_CTRL_DPLL_MANU_REF_CFG,
+ &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg));
+ if (err)
+ return err;
+
+ dpll_manu_ref_cfg &= ~(MANUAL_REFERENCE_MASK << MANUAL_REFERENCE_SHIFT);
+
+ dpll_manu_ref_cfg |= (ref << MANUAL_REFERENCE_SHIFT);
+
+ err = idtcm_write(idtcm, channel->dpll_ctrl_n,
+ DPLL_CTRL_DPLL_MANU_REF_CFG,
+ &dpll_manu_ref_cfg, sizeof(dpll_manu_ref_cfg));
+
+ return err;
+}
+
+static int configure_dpll_mode_write_frequency(struct idtcm_channel *channel)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_FREQUENCY);
+
+ if (err)
+ dev_err(idtcm->dev, "Failed to set pll mode to write frequency");
+ else
+ channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY;
+
+ return err;
+}
+
+static int configure_dpll_mode_write_phase(struct idtcm_channel *channel)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_PHASE);
+
+ if (err)
+ dev_err(idtcm->dev, "Failed to set pll mode to write phase");
+ else
+ channel->mode = PTP_PLL_MODE_WRITE_PHASE;
+
+ return err;
+}
+
+static int configure_manual_reference_write_frequency(struct idtcm_channel *channel)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ err = idtcm_set_manual_reference(channel, MANU_REF_WRITE_FREQUENCY);
+
+ if (err)
+ dev_err(idtcm->dev, "Failed to set manual reference to write frequency");
+ else
+ channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY;
+
+ return err;
+}
+
+static int configure_manual_reference_write_phase(struct idtcm_channel *channel)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ err = idtcm_set_manual_reference(channel, MANU_REF_WRITE_PHASE);
+
+ if (err)
+ dev_err(idtcm->dev, "Failed to set manual reference to write phase");
+ else
+ channel->mode = PTP_PLL_MODE_WRITE_PHASE;
+
+ return err;
+}
+
+static int idtcm_stop_phase_pull_in(struct idtcm_channel *channel)
+{
+ int err;
+
+ err = _idtcm_adjfine(channel, channel->current_freq_scaled_ppm);
+ if (err)
+ return err;
+
+ channel->phase_pull_in = false;
+
+ return 0;
+}
+
+static long idtcm_work_handler(struct ptp_clock_info *ptp)
+{
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm *idtcm = channel->idtcm;
+
+ mutex_lock(idtcm->lock);
+
+ (void)idtcm_stop_phase_pull_in(channel);
+
+ mutex_unlock(idtcm->lock);
+
+ /* Return a negative value here to not reschedule */
+ return -1;
+}
+
+static s32 phase_pull_in_scaled_ppm(s32 current_ppm, s32 phase_pull_in_ppb)
+{
+ /* ppb = scaled_ppm * 125 / 2^13 */
+ /* scaled_ppm = ppb * 2^13 / 125 */
+
+ s64 max_scaled_ppm = div_s64((s64)PHASE_PULL_IN_MAX_PPB << 13, 125);
+ s64 scaled_ppm = div_s64((s64)phase_pull_in_ppb << 13, 125);
+
+ current_ppm += scaled_ppm;
+
+ if (current_ppm > max_scaled_ppm)
+ current_ppm = max_scaled_ppm;
+ else if (current_ppm < -max_scaled_ppm)
+ current_ppm = -max_scaled_ppm;
+
+ return current_ppm;
+}
+
+static int do_phase_pull_in_sw(struct idtcm_channel *channel,
+ s32 delta_ns,
+ u32 max_ffo_ppb)
+{
+ s32 current_ppm = channel->current_freq_scaled_ppm;
+ u32 duration_ms = MSEC_PER_SEC;
+ s32 delta_ppm;
+ s32 ppb;
+ int err;
+
+ /* If the ToD correction is less than PHASE_PULL_IN_MIN_THRESHOLD_NS,
+ * skip. The error introduced by the ToD adjustment procedure would
+ * be bigger than the required ToD correction
+ */
+ if (abs(delta_ns) < PHASE_PULL_IN_MIN_THRESHOLD_NS)
+ return 0;
+
+ if (max_ffo_ppb == 0)
+ max_ffo_ppb = PHASE_PULL_IN_MAX_PPB;
+
+ /* For most cases, keep phase pull-in duration 1 second */
+ ppb = delta_ns;
+ while (abs(ppb) > max_ffo_ppb) {
+ duration_ms *= 2;
+ ppb /= 2;
+ }
+
+ delta_ppm = phase_pull_in_scaled_ppm(current_ppm, ppb);
+
+ err = _idtcm_adjfine(channel, delta_ppm);
+
+ if (err)
+ return err;
+
+ /* schedule the worker to cancel phase pull-in */
+ ptp_schedule_worker(channel->ptp_clock,
+ msecs_to_jiffies(duration_ms) - 1);
+
+ channel->phase_pull_in = true;
+
+ return 0;
+}
+
+static int initialize_operating_mode_with_manual_reference(struct idtcm_channel *channel,
+ enum manual_reference ref)
+{
+ struct idtcm *idtcm = channel->idtcm;
+
+ channel->mode = PTP_PLL_MODE_UNSUPPORTED;
+ channel->configure_write_frequency = configure_manual_reference_write_frequency;
+ channel->configure_write_phase = configure_manual_reference_write_phase;
+ channel->do_phase_pull_in = do_phase_pull_in_sw;
+
+ switch (ref) {
+ case MANU_REF_WRITE_PHASE:
+ channel->mode = PTP_PLL_MODE_WRITE_PHASE;
+ break;
+ case MANU_REF_WRITE_FREQUENCY:
+ channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY;
+ break;
+ default:
+ dev_warn(idtcm->dev,
+ "Unsupported MANUAL_REFERENCE: 0x%02x", ref);
+ }
+
+ return 0;
+}
+
+static int initialize_operating_mode_with_pll_mode(struct idtcm_channel *channel,
+ enum pll_mode mode)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err = 0;
+
+ channel->mode = PTP_PLL_MODE_UNSUPPORTED;
+ channel->configure_write_frequency = configure_dpll_mode_write_frequency;
+ channel->configure_write_phase = configure_dpll_mode_write_phase;
+ channel->do_phase_pull_in = do_phase_pull_in_fw;
+
+ switch (mode) {
+ case PLL_MODE_WRITE_PHASE:
+ channel->mode = PTP_PLL_MODE_WRITE_PHASE;
+ break;
+ case PLL_MODE_WRITE_FREQUENCY:
+ channel->mode = PTP_PLL_MODE_WRITE_FREQUENCY;
+ break;
+ default:
+ dev_err(idtcm->dev,
+ "Unsupported PLL_MODE: 0x%02x", mode);
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int initialize_dco_operating_mode(struct idtcm_channel *channel)
+{
+ enum manual_reference ref = MANU_REF_XO_DPLL;
+ enum pll_mode mode = PLL_MODE_DISABLED;
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ channel->mode = PTP_PLL_MODE_UNSUPPORTED;
+
+ err = idtcm_get_pll_mode(channel, &mode);
+ if (err) {
+ dev_err(idtcm->dev, "Unable to read pll mode!");
+ return err;
+ }
+
+ if (mode == PLL_MODE_PLL) {
+ err = idtcm_get_manual_reference(channel, &ref);
+ if (err) {
+ dev_err(idtcm->dev, "Unable to read manual reference!");
+ return err;
+ }
+ err = initialize_operating_mode_with_manual_reference(channel, ref);
+ } else {
+ err = initialize_operating_mode_with_pll_mode(channel, mode);
+ }
+
+ if (channel->mode == PTP_PLL_MODE_WRITE_PHASE)
+ channel->configure_write_frequency(channel);
+
+ return err;
+}
+
/* PTP Hardware Clock interface */
-static int idtcm_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
+/*
+ * Maximum absolute value for write phase offset in picoseconds
+ *
+ * @channel: channel
+ * @delta_ns: delta in nanoseconds
+ *
+ * Destination signed register is 32-bit register in resolution of 50ps
+ *
+ * 0x7fffffff * 50 = 2147483647 * 50 = 107374182350
+ */
+static int _idtcm_adjphase(struct idtcm_channel *channel, s32 delta_ns)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+ u8 i;
+ u8 buf[4] = {0};
+ s32 phase_50ps;
+ s64 offset_ps;
+
+ if (channel->mode != PTP_PLL_MODE_WRITE_PHASE) {
+ err = channel->configure_write_phase(channel);
+ if (err)
+ return err;
+ }
+
+ offset_ps = (s64)delta_ns * 1000;
+
+ /*
+ * Check for 32-bit signed max * 50:
+ *
+ * 0x7fffffff * 50 = 2147483647 * 50 = 107374182350
+ */
+ if (offset_ps > MAX_ABS_WRITE_PHASE_PICOSECONDS)
+ offset_ps = MAX_ABS_WRITE_PHASE_PICOSECONDS;
+ else if (offset_ps < -MAX_ABS_WRITE_PHASE_PICOSECONDS)
+ offset_ps = -MAX_ABS_WRITE_PHASE_PICOSECONDS;
+
+ phase_50ps = div_s64(offset_ps, 50);
+
+ for (i = 0; i < 4; i++) {
+ buf[i] = phase_50ps & 0xff;
+ phase_50ps >>= 8;
+ }
+
+ err = idtcm_write(idtcm, channel->dpll_phase, DPLL_WR_PHASE,
+ buf, sizeof(buf));
+
+ return err;
+}
+
+static int _idtcm_adjfine(struct idtcm_channel *channel, long scaled_ppm)
{
- struct idtcm_channel *channel =
- container_of(ptp, struct idtcm_channel, caps);
struct idtcm *idtcm = channel->idtcm;
u8 i;
- bool neg_adj = 0;
int err;
u8 buf[6] = {0};
s64 fcw;
- if (channel->pll_mode != PLL_MODE_WRITE_FREQUENCY) {
- err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_FREQUENCY);
+ if (channel->mode != PTP_PLL_MODE_WRITE_FREQUENCY) {
+ err = channel->configure_write_frequency(channel);
if (err)
return err;
}
@@ -901,45 +1768,54 @@ static int idtcm_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
* FCW = -------------
* 111 * 2^4
*/
- if (ppb < 0) {
- neg_adj = 1;
- ppb = -ppb;
- }
/* 2 ^ -53 = 1.1102230246251565404236316680908e-16 */
- fcw = ppb * 1000000000000ULL;
-
- fcw = div_u64(fcw, 111022);
+ fcw = scaled_ppm * 244140625ULL;
- if (neg_adj)
- fcw = -fcw;
+ fcw = div_s64(fcw, 1776);
for (i = 0; i < 6; i++) {
buf[i] = fcw & 0xff;
fcw >>= 8;
}
- mutex_lock(&idtcm->reg_lock);
-
err = idtcm_write(idtcm, channel->dpll_freq, DPLL_WR_FREQ,
buf, sizeof(buf));
- mutex_unlock(&idtcm->reg_lock);
return err;
}
static int idtcm_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
- struct idtcm_channel *channel =
- container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
struct idtcm *idtcm = channel->idtcm;
int err;
- mutex_lock(&idtcm->reg_lock);
+ mutex_lock(idtcm->lock);
+ err = _idtcm_gettime_immediate(channel, ts);
+ mutex_unlock(idtcm->lock);
- err = _idtcm_gettime(channel, ts);
+ if (err)
+ dev_err(idtcm->dev, "Failed at line %d in %s!",
+ __LINE__, __func__);
+
+ return err;
+}
- mutex_unlock(&idtcm->reg_lock);
+static int idtcm_settime_deprecated(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ mutex_lock(idtcm->lock);
+ err = _idtcm_settime_deprecated(channel, ts);
+ mutex_unlock(idtcm->lock);
+
+ if (err)
+ dev_err(idtcm->dev,
+ "Failed at line %d in %s!", __LINE__, __func__);
return err;
}
@@ -947,32 +1823,111 @@ static int idtcm_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
static int idtcm_settime(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
- struct idtcm_channel *channel =
- container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
struct idtcm *idtcm = channel->idtcm;
int err;
- mutex_lock(&idtcm->reg_lock);
+ mutex_lock(idtcm->lock);
+ err = _idtcm_settime(channel, ts, SCSR_TOD_WR_TYPE_SEL_ABSOLUTE);
+ mutex_unlock(idtcm->lock);
- err = _idtcm_settime(channel, ts, HW_TOD_WR_TRIG_SEL_MSB);
+ if (err)
+ dev_err(idtcm->dev,
+ "Failed at line %d in %s!", __LINE__, __func__);
- mutex_unlock(&idtcm->reg_lock);
+ return err;
+}
+
+static int idtcm_adjtime_deprecated(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ mutex_lock(idtcm->lock);
+ err = _idtcm_adjtime_deprecated(channel, delta);
+ mutex_unlock(idtcm->lock);
+
+ if (err)
+ dev_err(idtcm->dev,
+ "Failed at line %d in %s!", __LINE__, __func__);
return err;
}
static int idtcm_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
- struct idtcm_channel *channel =
- container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
struct idtcm *idtcm = channel->idtcm;
+ struct timespec64 ts;
+ enum scsr_tod_write_type_sel type;
int err;
- mutex_lock(&idtcm->reg_lock);
+ if (channel->phase_pull_in == true)
+ return -EBUSY;
- err = _idtcm_adjtime(channel, delta);
+ mutex_lock(idtcm->lock);
- mutex_unlock(&idtcm->reg_lock);
+ if (abs(delta) < PHASE_PULL_IN_THRESHOLD_NS) {
+ err = channel->do_phase_pull_in(channel, delta, 0);
+ } else {
+ if (delta >= 0) {
+ ts = ns_to_timespec64(delta);
+ type = SCSR_TOD_WR_TYPE_SEL_DELTA_PLUS;
+ } else {
+ ts = ns_to_timespec64(-delta);
+ type = SCSR_TOD_WR_TYPE_SEL_DELTA_MINUS;
+ }
+ err = _idtcm_settime(channel, &ts, type);
+ }
+
+ mutex_unlock(idtcm->lock);
+
+ if (err)
+ dev_err(idtcm->dev,
+ "Failed at line %d in %s!", __LINE__, __func__);
+
+ return err;
+}
+
+static int idtcm_adjphase(struct ptp_clock_info *ptp, s32 delta)
+{
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ mutex_lock(idtcm->lock);
+ err = _idtcm_adjphase(channel, delta);
+ mutex_unlock(idtcm->lock);
+
+ if (err)
+ dev_err(idtcm->dev,
+ "Failed at line %d in %s!", __LINE__, __func__);
+
+ return err;
+}
+
+static int idtcm_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm *idtcm = channel->idtcm;
+ int err;
+
+ if (channel->phase_pull_in == true)
+ return 0;
+
+ if (scaled_ppm == channel->current_freq_scaled_ppm)
+ return 0;
+
+ mutex_lock(idtcm->lock);
+ err = _idtcm_adjfine(channel, scaled_ppm);
+ mutex_unlock(idtcm->lock);
+
+ if (err)
+ dev_err(idtcm->dev,
+ "Failed at line %d in %s!", __LINE__, __func__);
+ else
+ channel->current_freq_scaled_ppm = scaled_ppm;
return err;
}
@@ -980,55 +1935,68 @@ static int idtcm_adjtime(struct ptp_clock_info *ptp, s64 delta)
static int idtcm_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
- struct idtcm_channel *channel =
- container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm_channel *channel = container_of(ptp, struct idtcm_channel, caps);
+ struct idtcm *idtcm = channel->idtcm;
+ int err = -EOPNOTSUPP;
+
+ mutex_lock(idtcm->lock);
switch (rq->type) {
case PTP_CLK_REQ_PEROUT:
if (!on)
- return idtcm_pps_enable(channel, false);
-
+ err = idtcm_perout_enable(channel, &rq->perout, false);
/* Only accept a 1-PPS aligned to the second. */
- if (rq->perout.start.nsec || rq->perout.period.sec != 1 ||
- rq->perout.period.nsec)
- return -ERANGE;
-
- return idtcm_pps_enable(channel, true);
+ else if (rq->perout.start.nsec || rq->perout.period.sec != 1 ||
+ rq->perout.period.nsec)
+ err = -ERANGE;
+ else
+ err = idtcm_perout_enable(channel, &rq->perout, true);
+ break;
+ case PTP_CLK_REQ_EXTTS:
+ err = idtcm_extts_enable(channel, rq, on);
+ break;
default:
break;
}
- return -EOPNOTSUPP;
+ mutex_unlock(idtcm->lock);
+
+ if (err)
+ dev_err(channel->idtcm->dev,
+ "Failed in %s with err %d!", __func__, err);
+
+ return err;
}
static int idtcm_enable_tod(struct idtcm_channel *channel)
{
struct idtcm *idtcm = channel->idtcm;
struct timespec64 ts = {0, 0};
+ u16 tod_cfg = IDTCM_FW_REG(idtcm->fw_ver, V520, TOD_CFG);
u8 cfg;
int err;
- err = idtcm_pps_enable(channel, false);
- if (err)
- return err;
-
/*
* Start the TOD clock ticking.
*/
- err = idtcm_read(idtcm, channel->tod_n, TOD_CFG, &cfg, sizeof(cfg));
+ err = idtcm_read(idtcm, channel->tod_n, tod_cfg, &cfg, sizeof(cfg));
if (err)
return err;
cfg |= TOD_ENABLE;
- err = idtcm_write(idtcm, channel->tod_n, TOD_CFG, &cfg, sizeof(cfg));
+ err = idtcm_write(idtcm, channel->tod_n, tod_cfg, &cfg, sizeof(cfg));
if (err)
return err;
- return _idtcm_settime(channel, &ts, HW_TOD_WR_TRIG_SEL_MSB);
+ if (idtcm->fw_ver < V487)
+ return _idtcm_settime_deprecated(channel, &ts);
+ else
+ return _idtcm_settime(channel, &ts,
+ SCSR_TOD_WR_TYPE_SEL_ABSOLUTE);
}
-static void idtcm_display_version_info(struct idtcm *idtcm)
+static void idtcm_set_version_info(struct idtcm *idtcm)
{
u8 major;
u8 minor;
@@ -1036,7 +2004,6 @@ static void idtcm_display_version_info(struct idtcm *idtcm)
u16 product_id;
u8 hw_rev_id;
u8 config_select;
- char *fmt = "%d.%d.%d, Id: 0x%04x HW Rev: %d OTP Config Select: %d\n";
idtcm_read_major_release(idtcm, &major);
idtcm_read_minor_release(idtcm, &minor);
@@ -1047,38 +2014,74 @@ static void idtcm_display_version_info(struct idtcm *idtcm)
idtcm_read_otp_scsr_config_select(idtcm, &config_select);
- dev_info(&idtcm->client->dev, fmt, major, minor, hotfix,
+ snprintf(idtcm->version, sizeof(idtcm->version), "%u.%u.%u",
+ major, minor, hotfix);
+
+ idtcm->fw_ver = idtcm_fw_version(idtcm->version);
+
+ dev_info(idtcm->dev,
+ "%d.%d.%d, Id: 0x%04x HW Rev: %d OTP Config Select: %d",
+ major, minor, hotfix,
product_id, hw_rev_id, config_select);
}
+static int idtcm_verify_pin(struct ptp_clock_info *ptp, unsigned int pin,
+ enum ptp_pin_function func, unsigned int chan)
+{
+ switch (func) {
+ case PTP_PF_NONE:
+ case PTP_PF_EXTTS:
+ break;
+ case PTP_PF_PEROUT:
+ case PTP_PF_PHYSYNC:
+ return -1;
+ }
+ return 0;
+}
+
+static struct ptp_pin_desc pin_config[MAX_TOD][MAX_REF_CLK];
+
static const struct ptp_clock_info idtcm_caps = {
.owner = THIS_MODULE,
.max_adj = 244000,
- .n_per_out = 1,
- .adjfreq = &idtcm_adjfreq,
+ .n_per_out = 12,
+ .n_ext_ts = MAX_TOD,
+ .n_pins = MAX_REF_CLK,
+ .adjphase = &idtcm_adjphase,
+ .adjfine = &idtcm_adjfine,
.adjtime = &idtcm_adjtime,
.gettime64 = &idtcm_gettime,
.settime64 = &idtcm_settime,
.enable = &idtcm_enable,
+ .verify = &idtcm_verify_pin,
+ .do_aux_work = &idtcm_work_handler,
};
-static int idtcm_enable_channel(struct idtcm *idtcm, u32 index)
-{
- struct idtcm_channel *channel;
- int err;
-
- if (!(index < MAX_PHC_PLL))
- return -EINVAL;
+static const struct ptp_clock_info idtcm_caps_deprecated = {
+ .owner = THIS_MODULE,
+ .max_adj = 244000,
+ .n_per_out = 12,
+ .n_ext_ts = MAX_TOD,
+ .n_pins = MAX_REF_CLK,
+ .adjphase = &idtcm_adjphase,
+ .adjfine = &idtcm_adjfine,
+ .adjtime = &idtcm_adjtime_deprecated,
+ .gettime64 = &idtcm_gettime,
+ .settime64 = &idtcm_settime_deprecated,
+ .enable = &idtcm_enable,
+ .verify = &idtcm_verify_pin,
+ .do_aux_work = &idtcm_work_handler,
+};
- channel = &idtcm->channel[index];
+static int configure_channel_pll(struct idtcm_channel *channel)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ int err = 0;
- switch (index) {
+ switch (channel->pll) {
case 0:
channel->dpll_freq = DPLL_FREQ_0;
channel->dpll_n = DPLL_0;
- channel->tod_read_primary = TOD_READ_PRIMARY_0;
- channel->tod_write = TOD_WRITE_0;
- channel->tod_n = TOD_0;
channel->hw_dpll_n = HW_DPLL_0;
channel->dpll_phase = DPLL_PHASE_0;
channel->dpll_ctrl_n = DPLL_CTRL_0;
@@ -1087,9 +2090,6 @@ static int idtcm_enable_channel(struct idtcm *idtcm, u32 index)
case 1:
channel->dpll_freq = DPLL_FREQ_1;
channel->dpll_n = DPLL_1;
- channel->tod_read_primary = TOD_READ_PRIMARY_1;
- channel->tod_write = TOD_WRITE_1;
- channel->tod_n = TOD_1;
channel->hw_dpll_n = HW_DPLL_1;
channel->dpll_phase = DPLL_PHASE_1;
channel->dpll_ctrl_n = DPLL_CTRL_1;
@@ -1097,10 +2097,7 @@ static int idtcm_enable_channel(struct idtcm *idtcm, u32 index)
break;
case 2:
channel->dpll_freq = DPLL_FREQ_2;
- channel->dpll_n = DPLL_2;
- channel->tod_read_primary = TOD_READ_PRIMARY_2;
- channel->tod_write = TOD_WRITE_2;
- channel->tod_n = TOD_2;
+ channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_2);
channel->hw_dpll_n = HW_DPLL_2;
channel->dpll_phase = DPLL_PHASE_2;
channel->dpll_ctrl_n = DPLL_CTRL_2;
@@ -1109,31 +2106,184 @@ static int idtcm_enable_channel(struct idtcm *idtcm, u32 index)
case 3:
channel->dpll_freq = DPLL_FREQ_3;
channel->dpll_n = DPLL_3;
- channel->tod_read_primary = TOD_READ_PRIMARY_3;
- channel->tod_write = TOD_WRITE_3;
- channel->tod_n = TOD_3;
channel->hw_dpll_n = HW_DPLL_3;
channel->dpll_phase = DPLL_PHASE_3;
channel->dpll_ctrl_n = DPLL_CTRL_3;
channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_3;
break;
+ case 4:
+ channel->dpll_freq = DPLL_FREQ_4;
+ channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_4);
+ channel->hw_dpll_n = HW_DPLL_4;
+ channel->dpll_phase = DPLL_PHASE_4;
+ channel->dpll_ctrl_n = DPLL_CTRL_4;
+ channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_4;
+ break;
+ case 5:
+ channel->dpll_freq = DPLL_FREQ_5;
+ channel->dpll_n = DPLL_5;
+ channel->hw_dpll_n = HW_DPLL_5;
+ channel->dpll_phase = DPLL_PHASE_5;
+ channel->dpll_ctrl_n = DPLL_CTRL_5;
+ channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_5;
+ break;
+ case 6:
+ channel->dpll_freq = DPLL_FREQ_6;
+ channel->dpll_n = IDTCM_FW_REG(idtcm->fw_ver, V520, DPLL_6);
+ channel->hw_dpll_n = HW_DPLL_6;
+ channel->dpll_phase = DPLL_PHASE_6;
+ channel->dpll_ctrl_n = DPLL_CTRL_6;
+ channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_6;
+ break;
+ case 7:
+ channel->dpll_freq = DPLL_FREQ_7;
+ channel->dpll_n = DPLL_7;
+ channel->hw_dpll_n = HW_DPLL_7;
+ channel->dpll_phase = DPLL_PHASE_7;
+ channel->dpll_ctrl_n = DPLL_CTRL_7;
+ channel->dpll_phase_pull_in = DPLL_PHASE_PULL_IN_7;
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+/*
+ * Compensate for the PTP DCO input-to-output delay.
+ * This delay is 18 FOD cycles.
+ */
+static u32 idtcm_get_dco_delay(struct idtcm_channel *channel)
+{
+ struct idtcm *idtcm = channel->idtcm;
+ u8 mbuf[8] = {0};
+ u8 nbuf[2] = {0};
+ u32 fodFreq;
+ int err;
+ u64 m;
+ u16 n;
+
+ err = idtcm_read(idtcm, channel->dpll_ctrl_n,
+ DPLL_CTRL_DPLL_FOD_FREQ, mbuf, 6);
+ if (err)
+ return 0;
+
+ err = idtcm_read(idtcm, channel->dpll_ctrl_n,
+ DPLL_CTRL_DPLL_FOD_FREQ + 6, nbuf, 2);
+ if (err)
+ return 0;
+
+ m = get_unaligned_le64(mbuf);
+ n = get_unaligned_le16(nbuf);
+
+ if (n == 0)
+ n = 1;
+
+ fodFreq = (u32)div_u64(m, n);
+
+ if (fodFreq >= 500000000)
+ return (u32)div_u64(18 * (u64)NSEC_PER_SEC, fodFreq);
+
+ return 0;
+}
+
+static int configure_channel_tod(struct idtcm_channel *channel, u32 index)
+{
+ enum fw_version fw_ver = channel->idtcm->fw_ver;
+
+ /* Set tod addresses */
+ switch (index) {
+ case 0:
+ channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_0);
+ channel->tod_read_secondary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_SECONDARY_0);
+ channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_0);
+ channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_0);
+ channel->sync_src = SYNC_SOURCE_DPLL0_TOD_PPS;
+ break;
+ case 1:
+ channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_1);
+ channel->tod_read_secondary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_SECONDARY_1);
+ channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_1);
+ channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_1);
+ channel->sync_src = SYNC_SOURCE_DPLL1_TOD_PPS;
+ break;
+ case 2:
+ channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_2);
+ channel->tod_read_secondary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_SECONDARY_2);
+ channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_2);
+ channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_2);
+ channel->sync_src = SYNC_SOURCE_DPLL2_TOD_PPS;
+ break;
+ case 3:
+ channel->tod_read_primary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_PRIMARY_3);
+ channel->tod_read_secondary = IDTCM_FW_REG(fw_ver, V520, TOD_READ_SECONDARY_3);
+ channel->tod_write = IDTCM_FW_REG(fw_ver, V520, TOD_WRITE_3);
+ channel->tod_n = IDTCM_FW_REG(fw_ver, V520, TOD_3);
+ channel->sync_src = SYNC_SOURCE_DPLL3_TOD_PPS;
+ break;
default:
return -EINVAL;
}
+ return 0;
+}
+
+static int idtcm_enable_channel(struct idtcm *idtcm, u32 index)
+{
+ struct idtcm_channel *channel;
+ int err;
+ int i;
+
+ if (!(index < MAX_TOD))
+ return -EINVAL;
+
+ channel = &idtcm->channel[index];
+
channel->idtcm = idtcm;
+ channel->current_freq_scaled_ppm = 0;
+
+ /* Set pll addresses */
+ err = configure_channel_pll(channel);
+ if (err)
+ return err;
+
+ /* Set tod addresses */
+ err = configure_channel_tod(channel, index);
+ if (err)
+ return err;
+
+ if (idtcm->fw_ver < V487)
+ channel->caps = idtcm_caps_deprecated;
+ else
+ channel->caps = idtcm_caps;
- channel->caps = idtcm_caps;
snprintf(channel->caps.name, sizeof(channel->caps.name),
- "IDT CM PLL%u", index);
+ "IDT CM TOD%u", index);
- err = idtcm_set_pll_mode(channel, PLL_MODE_WRITE_FREQUENCY);
+ channel->caps.pin_config = pin_config[index];
+
+ for (i = 0; i < channel->caps.n_pins; ++i) {
+ struct ptp_pin_desc *ppd = &channel->caps.pin_config[i];
+
+ snprintf(ppd->name, sizeof(ppd->name), "input_ref%d", i);
+ ppd->index = i;
+ ppd->func = PTP_PF_NONE;
+ ppd->chan = index;
+ }
+
+ err = initialize_dco_operating_mode(channel);
if (err)
return err;
err = idtcm_enable_tod(channel);
- if (err)
+ if (err) {
+ dev_err(idtcm->dev,
+ "Failed at line %d in %s!", __LINE__, __func__);
return err;
+ }
+
+ channel->dco_delay = idtcm_get_dco_delay(channel);
channel->ptp_clock = ptp_clock_register(&channel->caps, NULL);
@@ -1146,231 +2296,186 @@ static int idtcm_enable_channel(struct idtcm *idtcm, u32 index)
if (!channel->ptp_clock)
return -ENOTSUPP;
- dev_info(&idtcm->client->dev, "PLL%d registered as ptp%d\n",
+ dev_info(idtcm->dev, "PLL%d registered as ptp%d",
index, channel->ptp_clock->index);
return 0;
}
-static void ptp_clock_unregister_all(struct idtcm *idtcm)
+static int idtcm_enable_extts_channel(struct idtcm *idtcm, u32 index)
{
- u8 i;
struct idtcm_channel *channel;
+ int err;
- for (i = 0; i < MAX_PHC_PLL; i++) {
+ if (!(index < MAX_TOD))
+ return -EINVAL;
- channel = &idtcm->channel[i];
+ channel = &idtcm->channel[index];
+ channel->idtcm = idtcm;
- if (channel->ptp_clock)
- ptp_clock_unregister(channel->ptp_clock);
- }
-}
+ /* Set tod addresses */
+ err = configure_channel_tod(channel, index);
+ if (err)
+ return err;
-static void set_default_masks(struct idtcm *idtcm)
-{
- idtcm->pll_mask = DEFAULT_PLL_MASK;
+ channel->idtcm = idtcm;
- idtcm->channel[0].output_mask = DEFAULT_OUTPUT_MASK_PLL0;
- idtcm->channel[1].output_mask = DEFAULT_OUTPUT_MASK_PLL1;
- idtcm->channel[2].output_mask = DEFAULT_OUTPUT_MASK_PLL2;
- idtcm->channel[3].output_mask = DEFAULT_OUTPUT_MASK_PLL3;
+ return 0;
}
-static int set_tod_write_overhead(struct idtcm *idtcm)
+static void idtcm_extts_check(struct work_struct *work)
{
+ struct idtcm *idtcm = container_of(work, struct idtcm, extts_work.work);
+ struct idtcm_channel *channel;
+ u8 mask;
int err;
- u8 i;
+ int i;
- s64 total_ns = 0;
+ if (idtcm->extts_mask == 0)
+ return;
- ktime_t start;
- ktime_t stop;
+ mutex_lock(idtcm->lock);
- char buf[TOD_BYTE_COUNT];
+ for (i = 0; i < MAX_TOD; i++) {
+ mask = 1 << i;
- struct idtcm_channel *channel = &idtcm->channel[2];
+ if ((idtcm->extts_mask & mask) == 0)
+ continue;
- /* Set page offset */
- idtcm_write(idtcm, channel->hw_dpll_n, HW_DPLL_TOD_OVR__0,
- buf, sizeof(buf));
-
- for (i = 0; i < TOD_WRITE_OVERHEAD_COUNT_MAX; i++) {
+ err = idtcm_extts_check_channel(idtcm, i);
- start = ktime_get_raw();
+ if (err == 0) {
+ /* trigger clears itself, so clear the mask */
+ if (idtcm->extts_single_shot) {
+ idtcm->extts_mask &= ~mask;
+ } else {
+ /* Re-arm */
+ channel = &idtcm->channel[i];
+ arm_tod_read_trig_sel_refclk(channel, channel->refn);
+ }
+ }
+ }
- err = idtcm_write(idtcm, channel->hw_dpll_n,
- HW_DPLL_TOD_OVR__0, buf, sizeof(buf));
+ if (idtcm->extts_mask)
+ schedule_delayed_work(&idtcm->extts_work,
+ msecs_to_jiffies(EXTTS_PERIOD_MS));
- if (err)
- return err;
+ mutex_unlock(idtcm->lock);
+}
- stop = ktime_get_raw();
+static void ptp_clock_unregister_all(struct idtcm *idtcm)
+{
+ u8 i;
+ struct idtcm_channel *channel;
- total_ns += ktime_to_ns(stop - start);
+ for (i = 0; i < MAX_TOD; i++) {
+ channel = &idtcm->channel[i];
+ if (channel->ptp_clock)
+ ptp_clock_unregister(channel->ptp_clock);
}
+}
- idtcm->tod_write_overhead_ns = div_s64(total_ns,
- TOD_WRITE_OVERHEAD_COUNT_MAX);
+static void set_default_masks(struct idtcm *idtcm)
+{
+ idtcm->tod_mask = DEFAULT_TOD_MASK;
+ idtcm->extts_mask = 0;
- return err;
+ idtcm->channel[0].tod = 0;
+ idtcm->channel[1].tod = 1;
+ idtcm->channel[2].tod = 2;
+ idtcm->channel[3].tod = 3;
+
+ idtcm->channel[0].pll = DEFAULT_TOD0_PTP_PLL;
+ idtcm->channel[1].pll = DEFAULT_TOD1_PTP_PLL;
+ idtcm->channel[2].pll = DEFAULT_TOD2_PTP_PLL;
+ idtcm->channel[3].pll = DEFAULT_TOD3_PTP_PLL;
+
+ idtcm->channel[0].output_mask = DEFAULT_OUTPUT_MASK_PLL0;
+ idtcm->channel[1].output_mask = DEFAULT_OUTPUT_MASK_PLL1;
+ idtcm->channel[2].output_mask = DEFAULT_OUTPUT_MASK_PLL2;
+ idtcm->channel[3].output_mask = DEFAULT_OUTPUT_MASK_PLL3;
}
-static int idtcm_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int idtcm_probe(struct platform_device *pdev)
{
+ struct rsmu_ddata *ddata = dev_get_drvdata(pdev->dev.parent);
struct idtcm *idtcm;
int err;
u8 i;
- /* Unused for now */
- (void)id;
-
- idtcm = devm_kzalloc(&client->dev, sizeof(struct idtcm), GFP_KERNEL);
+ idtcm = devm_kzalloc(&pdev->dev, sizeof(struct idtcm), GFP_KERNEL);
if (!idtcm)
return -ENOMEM;
- idtcm->client = client;
- idtcm->page_offset = 0xff;
+ idtcm->dev = &pdev->dev;
+ idtcm->mfd = pdev->dev.parent;
+ idtcm->lock = &ddata->lock;
+ idtcm->regmap = ddata->regmap;
idtcm->calculate_overhead_flag = 0;
- set_default_masks(idtcm);
-
- mutex_init(&idtcm->reg_lock);
- mutex_lock(&idtcm->reg_lock);
+ INIT_DELAYED_WORK(&idtcm->extts_work, idtcm_extts_check);
- idtcm_display_version_info(idtcm);
+ set_default_masks(idtcm);
- err = set_tod_write_overhead(idtcm);
+ mutex_lock(idtcm->lock);
- if (err) {
- mutex_unlock(&idtcm->reg_lock);
- return err;
- }
+ idtcm_set_version_info(idtcm);
- err = idtcm_load_firmware(idtcm, &client->dev);
+ err = idtcm_load_firmware(idtcm, &pdev->dev);
if (err)
- dev_warn(&idtcm->client->dev,
- "loading firmware failed with %d\n", err);
+ dev_warn(idtcm->dev, "loading firmware failed with %d", err);
+
+ wait_for_chip_ready(idtcm);
- if (idtcm->pll_mask) {
- for (i = 0; i < MAX_PHC_PLL; i++) {
- if (idtcm->pll_mask & (1 << i)) {
+ if (idtcm->tod_mask) {
+ for (i = 0; i < MAX_TOD; i++) {
+ if (idtcm->tod_mask & (1 << i))
err = idtcm_enable_channel(idtcm, i);
- if (err)
- break;
+ else
+ err = idtcm_enable_extts_channel(idtcm, i);
+ if (err) {
+ dev_err(idtcm->dev,
+ "idtcm_enable_channel %d failed!", i);
+ break;
}
}
} else {
- dev_err(&idtcm->client->dev,
- "no PLLs flagged as PHCs, nothing to do\n");
+ dev_err(idtcm->dev,
+ "no PLLs flagged as PHCs, nothing to do");
err = -ENODEV;
}
- mutex_unlock(&idtcm->reg_lock);
+ mutex_unlock(idtcm->lock);
if (err) {
ptp_clock_unregister_all(idtcm);
return err;
}
- i2c_set_clientdata(client, idtcm);
+ platform_set_drvdata(pdev, idtcm);
return 0;
}
-static int idtcm_remove(struct i2c_client *client)
+static int idtcm_remove(struct platform_device *pdev)
{
- struct idtcm *idtcm = i2c_get_clientdata(client);
+ struct idtcm *idtcm = platform_get_drvdata(pdev);
+ idtcm->extts_mask = 0;
ptp_clock_unregister_all(idtcm);
-
- mutex_destroy(&idtcm->reg_lock);
+ cancel_delayed_work_sync(&idtcm->extts_work);
return 0;
}
-#ifdef CONFIG_OF
-static const struct of_device_id idtcm_dt_id[] = {
- { .compatible = "idt,8a34000" },
- { .compatible = "idt,8a34001" },
- { .compatible = "idt,8a34002" },
- { .compatible = "idt,8a34003" },
- { .compatible = "idt,8a34004" },
- { .compatible = "idt,8a34005" },
- { .compatible = "idt,8a34006" },
- { .compatible = "idt,8a34007" },
- { .compatible = "idt,8a34008" },
- { .compatible = "idt,8a34009" },
- { .compatible = "idt,8a34010" },
- { .compatible = "idt,8a34011" },
- { .compatible = "idt,8a34012" },
- { .compatible = "idt,8a34013" },
- { .compatible = "idt,8a34014" },
- { .compatible = "idt,8a34015" },
- { .compatible = "idt,8a34016" },
- { .compatible = "idt,8a34017" },
- { .compatible = "idt,8a34018" },
- { .compatible = "idt,8a34019" },
- { .compatible = "idt,8a34040" },
- { .compatible = "idt,8a34041" },
- { .compatible = "idt,8a34042" },
- { .compatible = "idt,8a34043" },
- { .compatible = "idt,8a34044" },
- { .compatible = "idt,8a34045" },
- { .compatible = "idt,8a34046" },
- { .compatible = "idt,8a34047" },
- { .compatible = "idt,8a34048" },
- { .compatible = "idt,8a34049" },
- {},
-};
-MODULE_DEVICE_TABLE(of, idtcm_dt_id);
-#endif
-
-static const struct i2c_device_id idtcm_i2c_id[] = {
- { "8a34000" },
- { "8a34001" },
- { "8a34002" },
- { "8a34003" },
- { "8a34004" },
- { "8a34005" },
- { "8a34006" },
- { "8a34007" },
- { "8a34008" },
- { "8a34009" },
- { "8a34010" },
- { "8a34011" },
- { "8a34012" },
- { "8a34013" },
- { "8a34014" },
- { "8a34015" },
- { "8a34016" },
- { "8a34017" },
- { "8a34018" },
- { "8a34019" },
- { "8a34040" },
- { "8a34041" },
- { "8a34042" },
- { "8a34043" },
- { "8a34044" },
- { "8a34045" },
- { "8a34046" },
- { "8a34047" },
- { "8a34048" },
- { "8a34049" },
- {},
-};
-MODULE_DEVICE_TABLE(i2c, idtcm_i2c_id);
-
-static struct i2c_driver idtcm_driver = {
+static struct platform_driver idtcm_driver = {
.driver = {
- .of_match_table = of_match_ptr(idtcm_dt_id),
- .name = "idtcm",
+ .name = "8a3400x-phc",
},
- .probe = idtcm_probe,
- .remove = idtcm_remove,
- .id_table = idtcm_i2c_id,
+ .probe = idtcm_probe,
+ .remove = idtcm_remove,
};
-module_i2c_driver(idtcm_driver);
+module_platform_driver(idtcm_driver);
diff --git a/drivers/ptp/ptp_clockmatrix.h b/drivers/ptp/ptp_clockmatrix.h
index 6c1f93ab46f3..bf1e49409844 100644
--- a/drivers/ptp/ptp_clockmatrix.h
+++ b/drivers/ptp/ptp_clockmatrix.h
@@ -9,55 +9,73 @@
#define PTP_IDTCLOCKMATRIX_H
#include <linux/ktime.h>
-
-#include "idt8a340_reg.h"
+#include <linux/mfd/idt8a340_reg.h>
+#include <linux/ptp_clock.h>
+#include <linux/regmap.h>
#define FW_FILENAME "idtcm.bin"
-#define MAX_PHC_PLL 4
+#define MAX_TOD (4)
+#define MAX_PLL (8)
+#define MAX_REF_CLK (16)
+
+#define MAX_ABS_WRITE_PHASE_PICOSECONDS (107374182350LL)
-#define PLL_MASK_ADDR (0xFFA5)
-#define DEFAULT_PLL_MASK (0x04)
+#define TOD_MASK_ADDR (0xFFA5)
+#define DEFAULT_TOD_MASK (0x04)
#define SET_U16_LSB(orig, val8) (orig = (0xff00 & (orig)) | (val8))
#define SET_U16_MSB(orig, val8) (orig = (0x00ff & (orig)) | (val8 << 8))
-#define OUTPUT_MASK_PLL0_ADDR (0xFFB0)
-#define OUTPUT_MASK_PLL1_ADDR (0xFFB2)
-#define OUTPUT_MASK_PLL2_ADDR (0xFFB4)
-#define OUTPUT_MASK_PLL3_ADDR (0xFFB6)
+#define TOD0_PTP_PLL_ADDR (0xFFA8)
+#define TOD1_PTP_PLL_ADDR (0xFFA9)
+#define TOD2_PTP_PLL_ADDR (0xFFAA)
+#define TOD3_PTP_PLL_ADDR (0xFFAB)
+
+#define TOD0_OUT_ALIGN_MASK_ADDR (0xFFB0)
+#define TOD1_OUT_ALIGN_MASK_ADDR (0xFFB2)
+#define TOD2_OUT_ALIGN_MASK_ADDR (0xFFB4)
+#define TOD3_OUT_ALIGN_MASK_ADDR (0xFFB6)
#define DEFAULT_OUTPUT_MASK_PLL0 (0x003)
#define DEFAULT_OUTPUT_MASK_PLL1 (0x00c)
#define DEFAULT_OUTPUT_MASK_PLL2 (0x030)
#define DEFAULT_OUTPUT_MASK_PLL3 (0x0c0)
-#define POST_SM_RESET_DELAY_MS (3000)
-#define PHASE_PULL_IN_THRESHOLD_NS (150000)
-#define TOD_WRITE_OVERHEAD_COUNT_MAX (5)
-#define TOD_BYTE_COUNT (11)
-
-/* Values of DPLL_N.DPLL_MODE.PLL_MODE */
-enum pll_mode {
- PLL_MODE_MIN = 0,
- PLL_MODE_NORMAL = PLL_MODE_MIN,
- PLL_MODE_WRITE_PHASE = 1,
- PLL_MODE_WRITE_FREQUENCY = 2,
- PLL_MODE_GPIO_INC_DEC = 3,
- PLL_MODE_SYNTHESIS = 4,
- PLL_MODE_PHASE_MEASUREMENT = 5,
- PLL_MODE_MAX = PLL_MODE_PHASE_MEASUREMENT,
+#define DEFAULT_TOD0_PTP_PLL (0)
+#define DEFAULT_TOD1_PTP_PLL (1)
+#define DEFAULT_TOD2_PTP_PLL (2)
+#define DEFAULT_TOD3_PTP_PLL (3)
+
+#define PHASE_PULL_IN_THRESHOLD_NS_DEPRECATED (150000)
+#define PHASE_PULL_IN_THRESHOLD_NS (15000)
+#define TOD_WRITE_OVERHEAD_COUNT_MAX (2)
+#define TOD_BYTE_COUNT (11)
+
+#define LOCK_TIMEOUT_MS (2000)
+#define LOCK_POLL_INTERVAL_MS (10)
+
+#define IDTCM_MAX_WRITE_COUNT (512)
+
+#define PHASE_PULL_IN_MAX_PPB (144000)
+#define PHASE_PULL_IN_MIN_THRESHOLD_NS (2)
+
+/*
+ * Return register address based on passed in firmware version
+ */
+#define IDTCM_FW_REG(FW, VER, REG) (((FW) < (VER)) ? (REG) : (REG##_##VER))
+enum fw_version {
+ V_DEFAULT = 0,
+ V487 = 1,
+ V520 = 2,
};
-enum hw_tod_write_trig_sel {
- HW_TOD_WR_TRIG_SEL_MIN = 0,
- HW_TOD_WR_TRIG_SEL_MSB = HW_TOD_WR_TRIG_SEL_MIN,
- HW_TOD_WR_TRIG_SEL_RESERVED = 1,
- HW_TOD_WR_TRIG_SEL_TOD_PPS = 2,
- HW_TOD_WR_TRIG_SEL_IRIGB_PPS = 3,
- HW_TOD_WR_TRIG_SEL_PWM_PPS = 4,
- HW_TOD_WR_TRIG_SEL_GPIO = 5,
- HW_TOD_WR_TRIG_SEL_FOD_SYNC = 6,
- WR_TRIG_SEL_MAX = HW_TOD_WR_TRIG_SEL_FOD_SYNC,
+/* PTP PLL Mode */
+enum ptp_pll_mode {
+ PTP_PLL_MODE_MIN = 0,
+ PTP_PLL_MODE_WRITE_FREQUENCY = PTP_PLL_MODE_MIN,
+ PTP_PLL_MODE_WRITE_PHASE,
+ PTP_PLL_MODE_UNSUPPORTED,
+ PTP_PLL_MODE_MAX = PTP_PLL_MODE_UNSUPPORTED,
};
struct idtcm;
@@ -72,26 +90,46 @@ struct idtcm_channel {
u16 dpll_ctrl_n;
u16 dpll_phase_pull_in;
u16 tod_read_primary;
+ u16 tod_read_secondary;
u16 tod_write;
u16 tod_n;
u16 hw_dpll_n;
- enum pll_mode pll_mode;
+ u8 sync_src;
+ enum ptp_pll_mode mode;
+ int (*configure_write_frequency)(struct idtcm_channel *channel);
+ int (*configure_write_phase)(struct idtcm_channel *channel);
+ int (*do_phase_pull_in)(struct idtcm_channel *channel,
+ s32 offset_ns, u32 max_ffo_ppb);
+ s32 current_freq_scaled_ppm;
+ bool phase_pull_in;
+ u32 dco_delay;
+ /* last input trigger for extts */
+ u8 refn;
+ u8 pll;
+ u8 tod;
u16 output_mask;
};
struct idtcm {
- struct idtcm_channel channel[MAX_PHC_PLL];
- struct i2c_client *client;
- u8 page_offset;
- u8 pll_mask;
-
+ struct idtcm_channel channel[MAX_TOD];
+ struct device *dev;
+ u8 tod_mask;
+ char version[16];
+ enum fw_version fw_ver;
+ /* Polls for external time stamps */
+ u8 extts_mask;
+ bool extts_single_shot;
+ struct delayed_work extts_work;
+ /* Remember the ptp channel to report extts */
+ struct idtcm_channel *event_channel[MAX_TOD];
+ /* Mutex to protect operations from being interrupted */
+ struct mutex *lock;
+ struct device *mfd;
+ struct regmap *regmap;
/* Overhead calculation for adjtime */
u8 calculate_overhead_flag;
s64 tod_write_overhead_ns;
ktime_t start_time;
-
- /* Protects I2C read/modify/write registers from concurrent access */
- struct mutex reg_lock;
};
struct idtcm_fwrc {
diff --git a/drivers/ptp/ptp_dte.c b/drivers/ptp/ptp_dte.c
index 82d31ba32690..8641fd060491 100644
--- a/drivers/ptp/ptp_dte.c
+++ b/drivers/ptp/ptp_dte.c
@@ -1,15 +1,5 @@
-/*
- * Copyright 2017 Broadcom
- *
- * This program is free software; you can redistribute it and/or
- * modify it 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.
- */
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright 2017 Broadcom
#include <linux/err.h>
#include <linux/io.h>
diff --git a/drivers/ptp/ptp_idt82p33.c b/drivers/ptp/ptp_idt82p33.c
new file mode 100644
index 000000000000..97c1be44e323
--- /dev/null
+++ b/drivers/ptp/ptp_idt82p33.c
@@ -0,0 +1,944 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 Integrated Device Technology, Inc
+//
+
+#define pr_fmt(fmt) "IDT_82p33xxx: " fmt
+
+#include <linux/firmware.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/timekeeping.h>
+#include <linux/bitops.h>
+#include <linux/of.h>
+#include <linux/mfd/rsmu.h>
+#include <linux/mfd/idt82p33_reg.h>
+
+#include "ptp_private.h"
+#include "ptp_idt82p33.h"
+
+MODULE_DESCRIPTION("Driver for IDT 82p33xxx clock devices");
+MODULE_AUTHOR("IDT support-1588 <IDT-support-1588@lm.renesas.com>");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FW_FILENAME);
+
+/* Module Parameters */
+static u32 phase_snap_threshold = SNAP_THRESHOLD_NS;
+module_param(phase_snap_threshold, uint, 0);
+MODULE_PARM_DESC(phase_snap_threshold,
+"threshold (10000ns by default) below which adjtime would use double dco");
+
+static char *firmware;
+module_param(firmware, charp, 0);
+
+static inline int idt82p33_read(struct idt82p33 *idt82p33, u16 regaddr,
+ u8 *buf, u16 count)
+{
+ return regmap_bulk_read(idt82p33->regmap, regaddr, buf, count);
+}
+
+static inline int idt82p33_write(struct idt82p33 *idt82p33, u16 regaddr,
+ u8 *buf, u16 count)
+{
+ return regmap_bulk_write(idt82p33->regmap, regaddr, buf, count);
+}
+
+static void idt82p33_byte_array_to_timespec(struct timespec64 *ts,
+ u8 buf[TOD_BYTE_COUNT])
+{
+ time64_t sec;
+ s32 nsec;
+ u8 i;
+
+ nsec = buf[3];
+ for (i = 0; i < 3; i++) {
+ nsec <<= 8;
+ nsec |= buf[2 - i];
+ }
+
+ sec = buf[9];
+ for (i = 0; i < 5; i++) {
+ sec <<= 8;
+ sec |= buf[8 - i];
+ }
+
+ ts->tv_sec = sec;
+ ts->tv_nsec = nsec;
+}
+
+static void idt82p33_timespec_to_byte_array(struct timespec64 const *ts,
+ u8 buf[TOD_BYTE_COUNT])
+{
+ time64_t sec;
+ s32 nsec;
+ u8 i;
+
+ nsec = ts->tv_nsec;
+ sec = ts->tv_sec;
+
+ for (i = 0; i < 4; i++) {
+ buf[i] = nsec & 0xff;
+ nsec >>= 8;
+ }
+
+ for (i = 4; i < TOD_BYTE_COUNT; i++) {
+ buf[i] = sec & 0xff;
+ sec >>= 8;
+ }
+}
+
+static int idt82p33_dpll_set_mode(struct idt82p33_channel *channel,
+ enum pll_mode mode)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ u8 dpll_mode;
+ int err;
+
+ if (channel->pll_mode == mode)
+ return 0;
+
+ err = idt82p33_read(idt82p33, channel->dpll_mode_cnfg,
+ &dpll_mode, sizeof(dpll_mode));
+ if (err)
+ return err;
+
+ dpll_mode &= ~(PLL_MODE_MASK << PLL_MODE_SHIFT);
+
+ dpll_mode |= (mode << PLL_MODE_SHIFT);
+
+ err = idt82p33_write(idt82p33, channel->dpll_mode_cnfg,
+ &dpll_mode, sizeof(dpll_mode));
+ if (err)
+ return err;
+
+ channel->pll_mode = mode;
+
+ return 0;
+}
+
+static int _idt82p33_gettime(struct idt82p33_channel *channel,
+ struct timespec64 *ts)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ u8 buf[TOD_BYTE_COUNT];
+ u8 trigger;
+ int err;
+
+ trigger = TOD_TRIGGER(HW_TOD_WR_TRIG_SEL_MSB_TOD_CNFG,
+ HW_TOD_RD_TRIG_SEL_LSB_TOD_STS);
+
+
+ err = idt82p33_write(idt82p33, channel->dpll_tod_trigger,
+ &trigger, sizeof(trigger));
+
+ if (err)
+ return err;
+
+ if (idt82p33->calculate_overhead_flag)
+ idt82p33->start_time = ktime_get_raw();
+
+ err = idt82p33_read(idt82p33, channel->dpll_tod_sts, buf, sizeof(buf));
+
+ if (err)
+ return err;
+
+ idt82p33_byte_array_to_timespec(ts, buf);
+
+ return 0;
+}
+
+/*
+ * TOD Trigger:
+ * Bits[7:4] Write 0x9, MSB write
+ * Bits[3:0] Read 0x9, LSB read
+ */
+
+static int _idt82p33_settime(struct idt82p33_channel *channel,
+ struct timespec64 const *ts)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ struct timespec64 local_ts = *ts;
+ char buf[TOD_BYTE_COUNT];
+ s64 dynamic_overhead_ns;
+ unsigned char trigger;
+ int err;
+ u8 i;
+
+ trigger = TOD_TRIGGER(HW_TOD_WR_TRIG_SEL_MSB_TOD_CNFG,
+ HW_TOD_RD_TRIG_SEL_LSB_TOD_STS);
+
+ err = idt82p33_write(idt82p33, channel->dpll_tod_trigger,
+ &trigger, sizeof(trigger));
+
+ if (err)
+ return err;
+
+ if (idt82p33->calculate_overhead_flag) {
+ dynamic_overhead_ns = ktime_to_ns(ktime_get_raw())
+ - ktime_to_ns(idt82p33->start_time);
+
+ timespec64_add_ns(&local_ts, dynamic_overhead_ns);
+
+ idt82p33->calculate_overhead_flag = 0;
+ }
+
+ idt82p33_timespec_to_byte_array(&local_ts, buf);
+
+ /*
+ * Store the new time value.
+ */
+ for (i = 0; i < TOD_BYTE_COUNT; i++) {
+ err = idt82p33_write(idt82p33, channel->dpll_tod_cnfg + i,
+ &buf[i], sizeof(buf[i]));
+ if (err)
+ return err;
+ }
+
+ return err;
+}
+
+static int _idt82p33_adjtime(struct idt82p33_channel *channel, s64 delta_ns)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ struct timespec64 ts;
+ s64 now_ns;
+ int err;
+
+ idt82p33->calculate_overhead_flag = 1;
+
+ err = _idt82p33_gettime(channel, &ts);
+
+ if (err)
+ return err;
+
+ now_ns = timespec64_to_ns(&ts);
+ now_ns += delta_ns + idt82p33->tod_write_overhead_ns;
+
+ ts = ns_to_timespec64(now_ns);
+
+ err = _idt82p33_settime(channel, &ts);
+
+ return err;
+}
+
+static int _idt82p33_adjfine(struct idt82p33_channel *channel, long scaled_ppm)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ unsigned char buf[5] = {0};
+ int err, i;
+ s64 fcw;
+
+ if (scaled_ppm == channel->current_freq_ppb)
+ return 0;
+
+ /*
+ * Frequency Control Word unit is: 1.68 * 10^-10 ppm
+ *
+ * adjfreq:
+ * ppb * 10^9
+ * FCW = ----------
+ * 168
+ *
+ * adjfine:
+ * scaled_ppm * 5^12
+ * FCW = -------------
+ * 168 * 2^4
+ */
+
+ fcw = scaled_ppm * 244140625ULL;
+ fcw = div_s64(fcw, 2688);
+
+ for (i = 0; i < 5; i++) {
+ buf[i] = fcw & 0xff;
+ fcw >>= 8;
+ }
+
+ err = idt82p33_dpll_set_mode(channel, PLL_MODE_DCO);
+
+ if (err)
+ return err;
+
+ err = idt82p33_write(idt82p33, channel->dpll_freq_cnfg,
+ buf, sizeof(buf));
+
+ if (err == 0)
+ channel->current_freq_ppb = scaled_ppm;
+
+ return err;
+}
+
+static int idt82p33_measure_one_byte_write_overhead(
+ struct idt82p33_channel *channel, s64 *overhead_ns)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ ktime_t start, stop;
+ s64 total_ns;
+ u8 trigger;
+ int err;
+ u8 i;
+
+ total_ns = 0;
+ *overhead_ns = 0;
+ trigger = TOD_TRIGGER(HW_TOD_WR_TRIG_SEL_MSB_TOD_CNFG,
+ HW_TOD_RD_TRIG_SEL_LSB_TOD_STS);
+
+ for (i = 0; i < MAX_MEASURMENT_COUNT; i++) {
+
+ start = ktime_get_raw();
+
+ err = idt82p33_write(idt82p33, channel->dpll_tod_trigger,
+ &trigger, sizeof(trigger));
+
+ stop = ktime_get_raw();
+
+ if (err)
+ return err;
+
+ total_ns += ktime_to_ns(stop) - ktime_to_ns(start);
+ }
+
+ *overhead_ns = div_s64(total_ns, MAX_MEASURMENT_COUNT);
+
+ return err;
+}
+
+static int idt82p33_measure_tod_write_9_byte_overhead(
+ struct idt82p33_channel *channel)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ u8 buf[TOD_BYTE_COUNT];
+ ktime_t start, stop;
+ s64 total_ns;
+ int err = 0;
+ u8 i, j;
+
+ total_ns = 0;
+ idt82p33->tod_write_overhead_ns = 0;
+
+ for (i = 0; i < MAX_MEASURMENT_COUNT; i++) {
+
+ start = ktime_get_raw();
+
+ /* Need one less byte for applicable overhead */
+ for (j = 0; j < (TOD_BYTE_COUNT - 1); j++) {
+ err = idt82p33_write(idt82p33,
+ channel->dpll_tod_cnfg + i,
+ &buf[i], sizeof(buf[i]));
+ if (err)
+ return err;
+ }
+
+ stop = ktime_get_raw();
+
+ total_ns += ktime_to_ns(stop) - ktime_to_ns(start);
+ }
+
+ idt82p33->tod_write_overhead_ns = div_s64(total_ns,
+ MAX_MEASURMENT_COUNT);
+
+ return err;
+}
+
+static int idt82p33_measure_settime_gettime_gap_overhead(
+ struct idt82p33_channel *channel, s64 *overhead_ns)
+{
+ struct timespec64 ts1 = {0, 0};
+ struct timespec64 ts2;
+ int err;
+
+ *overhead_ns = 0;
+
+ err = _idt82p33_settime(channel, &ts1);
+
+ if (err)
+ return err;
+
+ err = _idt82p33_gettime(channel, &ts2);
+
+ if (!err)
+ *overhead_ns = timespec64_to_ns(&ts2) - timespec64_to_ns(&ts1);
+
+ return err;
+}
+
+static int idt82p33_measure_tod_write_overhead(struct idt82p33_channel *channel)
+{
+ s64 trailing_overhead_ns, one_byte_write_ns, gap_ns;
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ int err;
+
+ idt82p33->tod_write_overhead_ns = 0;
+
+ err = idt82p33_measure_settime_gettime_gap_overhead(channel, &gap_ns);
+
+ if (err) {
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+ return err;
+ }
+
+ err = idt82p33_measure_one_byte_write_overhead(channel,
+ &one_byte_write_ns);
+
+ if (err)
+ return err;
+
+ err = idt82p33_measure_tod_write_9_byte_overhead(channel);
+
+ if (err)
+ return err;
+
+ trailing_overhead_ns = gap_ns - (2 * one_byte_write_ns);
+
+ idt82p33->tod_write_overhead_ns -= trailing_overhead_ns;
+
+ return err;
+}
+
+static int idt82p33_check_and_set_masks(struct idt82p33 *idt82p33,
+ u8 page,
+ u8 offset,
+ u8 val)
+{
+ int err = 0;
+
+ if (page == PLLMASK_ADDR_HI && offset == PLLMASK_ADDR_LO) {
+ if ((val & 0xfc) || !(val & 0x3)) {
+ dev_err(idt82p33->dev,
+ "Invalid PLL mask 0x%x\n", val);
+ err = -EINVAL;
+ } else {
+ idt82p33->pll_mask = val;
+ }
+ } else if (page == PLL0_OUTMASK_ADDR_HI &&
+ offset == PLL0_OUTMASK_ADDR_LO) {
+ idt82p33->channel[0].output_mask = val;
+ } else if (page == PLL1_OUTMASK_ADDR_HI &&
+ offset == PLL1_OUTMASK_ADDR_LO) {
+ idt82p33->channel[1].output_mask = val;
+ }
+
+ return err;
+}
+
+static void idt82p33_display_masks(struct idt82p33 *idt82p33)
+{
+ u8 mask, i;
+
+ dev_info(idt82p33->dev,
+ "pllmask = 0x%02x\n", idt82p33->pll_mask);
+
+ for (i = 0; i < MAX_PHC_PLL; i++) {
+ mask = 1 << i;
+
+ if (mask & idt82p33->pll_mask)
+ dev_info(idt82p33->dev,
+ "PLL%d output_mask = 0x%04x\n",
+ i, idt82p33->channel[i].output_mask);
+ }
+}
+
+static int idt82p33_sync_tod(struct idt82p33_channel *channel, bool enable)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ u8 sync_cnfg;
+ int err;
+
+ err = idt82p33_read(idt82p33, channel->dpll_sync_cnfg,
+ &sync_cnfg, sizeof(sync_cnfg));
+ if (err)
+ return err;
+
+ sync_cnfg &= ~SYNC_TOD;
+ if (enable)
+ sync_cnfg |= SYNC_TOD;
+
+ return idt82p33_write(idt82p33, channel->dpll_sync_cnfg,
+ &sync_cnfg, sizeof(sync_cnfg));
+}
+
+static int idt82p33_output_enable(struct idt82p33_channel *channel,
+ bool enable, unsigned int outn)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ int err;
+ u8 val;
+
+ err = idt82p33_read(idt82p33, OUT_MUX_CNFG(outn), &val, sizeof(val));
+ if (err)
+ return err;
+ if (enable)
+ val &= ~SQUELCH_ENABLE;
+ else
+ val |= SQUELCH_ENABLE;
+
+ return idt82p33_write(idt82p33, OUT_MUX_CNFG(outn), &val, sizeof(val));
+}
+
+static int idt82p33_output_mask_enable(struct idt82p33_channel *channel,
+ bool enable)
+{
+ u16 mask;
+ int err;
+ u8 outn;
+
+ mask = channel->output_mask;
+ outn = 0;
+
+ while (mask) {
+ if (mask & 0x1) {
+ err = idt82p33_output_enable(channel, enable, outn);
+ if (err)
+ return err;
+ }
+
+ mask >>= 0x1;
+ outn++;
+ }
+
+ return 0;
+}
+
+static int idt82p33_perout_enable(struct idt82p33_channel *channel,
+ bool enable,
+ struct ptp_perout_request *perout)
+{
+ unsigned int flags = perout->flags;
+
+ /* Enable/disable output based on output_mask */
+ if (flags == PEROUT_ENABLE_OUTPUT_MASK)
+ return idt82p33_output_mask_enable(channel, enable);
+
+ /* Enable/disable individual output instead */
+ return idt82p33_output_enable(channel, enable, perout->index);
+}
+
+static int idt82p33_enable_tod(struct idt82p33_channel *channel)
+{
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ struct timespec64 ts = {0, 0};
+ int err;
+
+ err = idt82p33_measure_tod_write_overhead(channel);
+
+ if (err) {
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+ return err;
+ }
+
+ err = _idt82p33_settime(channel, &ts);
+
+ if (err)
+ return err;
+
+ return idt82p33_sync_tod(channel, true);
+}
+
+static void idt82p33_ptp_clock_unregister_all(struct idt82p33 *idt82p33)
+{
+ struct idt82p33_channel *channel;
+ u8 i;
+
+ for (i = 0; i < MAX_PHC_PLL; i++) {
+
+ channel = &idt82p33->channel[i];
+
+ if (channel->ptp_clock)
+ ptp_clock_unregister(channel->ptp_clock);
+ }
+}
+
+static int idt82p33_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct idt82p33_channel *channel =
+ container_of(ptp, struct idt82p33_channel, caps);
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ int err = -EOPNOTSUPP;
+
+ mutex_lock(idt82p33->lock);
+
+ if (rq->type == PTP_CLK_REQ_PEROUT) {
+ if (!on)
+ err = idt82p33_perout_enable(channel, false,
+ &rq->perout);
+ /* Only accept a 1-PPS aligned to the second. */
+ else if (rq->perout.start.nsec || rq->perout.period.sec != 1 ||
+ rq->perout.period.nsec)
+ err = -ERANGE;
+ else
+ err = idt82p33_perout_enable(channel, true,
+ &rq->perout);
+ }
+
+ mutex_unlock(idt82p33->lock);
+
+ if (err)
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+ return err;
+}
+
+static int idt82p33_adjwritephase(struct ptp_clock_info *ptp, s32 offset_ns)
+{
+ struct idt82p33_channel *channel =
+ container_of(ptp, struct idt82p33_channel, caps);
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ s64 offset_regval, offset_fs;
+ u8 val[4] = {0};
+ int err;
+
+ offset_fs = (s64)(-offset_ns) * 1000000;
+
+ if (offset_fs > WRITE_PHASE_OFFSET_LIMIT)
+ offset_fs = WRITE_PHASE_OFFSET_LIMIT;
+ else if (offset_fs < -WRITE_PHASE_OFFSET_LIMIT)
+ offset_fs = -WRITE_PHASE_OFFSET_LIMIT;
+
+ /* Convert from phaseoffset_fs to register value */
+ offset_regval = div_s64(offset_fs * 1000, IDT_T0DPLL_PHASE_RESOL);
+
+ val[0] = offset_regval & 0xFF;
+ val[1] = (offset_regval >> 8) & 0xFF;
+ val[2] = (offset_regval >> 16) & 0xFF;
+ val[3] = (offset_regval >> 24) & 0x1F;
+ val[3] |= PH_OFFSET_EN;
+
+ mutex_lock(idt82p33->lock);
+
+ err = idt82p33_dpll_set_mode(channel, PLL_MODE_WPH);
+ if (err) {
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+ goto out;
+ }
+
+ err = idt82p33_write(idt82p33, channel->dpll_phase_cnfg, val,
+ sizeof(val));
+
+out:
+ mutex_unlock(idt82p33->lock);
+ return err;
+}
+
+static int idt82p33_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct idt82p33_channel *channel =
+ container_of(ptp, struct idt82p33_channel, caps);
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ int err;
+
+ mutex_lock(idt82p33->lock);
+ err = _idt82p33_adjfine(channel, scaled_ppm);
+ mutex_unlock(idt82p33->lock);
+ if (err)
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+
+ return err;
+}
+
+static int idt82p33_adjtime(struct ptp_clock_info *ptp, s64 delta_ns)
+{
+ struct idt82p33_channel *channel =
+ container_of(ptp, struct idt82p33_channel, caps);
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ int err;
+
+ mutex_lock(idt82p33->lock);
+
+ if (abs(delta_ns) < phase_snap_threshold) {
+ mutex_unlock(idt82p33->lock);
+ return 0;
+ }
+
+ err = _idt82p33_adjtime(channel, delta_ns);
+
+ mutex_unlock(idt82p33->lock);
+
+ if (err)
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+ return err;
+}
+
+static int idt82p33_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
+{
+ struct idt82p33_channel *channel =
+ container_of(ptp, struct idt82p33_channel, caps);
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ int err;
+
+ mutex_lock(idt82p33->lock);
+ err = _idt82p33_gettime(channel, ts);
+ mutex_unlock(idt82p33->lock);
+
+ if (err)
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+ return err;
+}
+
+static int idt82p33_settime(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct idt82p33_channel *channel =
+ container_of(ptp, struct idt82p33_channel, caps);
+ struct idt82p33 *idt82p33 = channel->idt82p33;
+ int err;
+
+ mutex_lock(idt82p33->lock);
+ err = _idt82p33_settime(channel, ts);
+ mutex_unlock(idt82p33->lock);
+
+ if (err)
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+ return err;
+}
+
+static int idt82p33_channel_init(struct idt82p33_channel *channel, int index)
+{
+ switch (index) {
+ case 0:
+ channel->dpll_tod_cnfg = DPLL1_TOD_CNFG;
+ channel->dpll_tod_trigger = DPLL1_TOD_TRIGGER;
+ channel->dpll_tod_sts = DPLL1_TOD_STS;
+ channel->dpll_mode_cnfg = DPLL1_OPERATING_MODE_CNFG;
+ channel->dpll_freq_cnfg = DPLL1_HOLDOVER_FREQ_CNFG;
+ channel->dpll_phase_cnfg = DPLL1_PHASE_OFFSET_CNFG;
+ channel->dpll_sync_cnfg = DPLL1_SYNC_EDGE_CNFG;
+ channel->dpll_input_mode_cnfg = DPLL1_INPUT_MODE_CNFG;
+ break;
+ case 1:
+ channel->dpll_tod_cnfg = DPLL2_TOD_CNFG;
+ channel->dpll_tod_trigger = DPLL2_TOD_TRIGGER;
+ channel->dpll_tod_sts = DPLL2_TOD_STS;
+ channel->dpll_mode_cnfg = DPLL2_OPERATING_MODE_CNFG;
+ channel->dpll_freq_cnfg = DPLL2_HOLDOVER_FREQ_CNFG;
+ channel->dpll_phase_cnfg = DPLL2_PHASE_OFFSET_CNFG;
+ channel->dpll_sync_cnfg = DPLL2_SYNC_EDGE_CNFG;
+ channel->dpll_input_mode_cnfg = DPLL2_INPUT_MODE_CNFG;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ channel->current_freq_ppb = 0;
+
+ return 0;
+}
+
+static void idt82p33_caps_init(struct ptp_clock_info *caps)
+{
+ caps->owner = THIS_MODULE;
+ caps->max_adj = DCO_MAX_PPB;
+ caps->n_per_out = 11;
+ caps->adjphase = idt82p33_adjwritephase;
+ caps->adjfine = idt82p33_adjfine;
+ caps->adjtime = idt82p33_adjtime;
+ caps->gettime64 = idt82p33_gettime;
+ caps->settime64 = idt82p33_settime;
+ caps->enable = idt82p33_enable;
+}
+
+static int idt82p33_enable_channel(struct idt82p33 *idt82p33, u32 index)
+{
+ struct idt82p33_channel *channel;
+ int err;
+
+ if (!(index < MAX_PHC_PLL))
+ return -EINVAL;
+
+ channel = &idt82p33->channel[index];
+
+ err = idt82p33_channel_init(channel, index);
+ if (err) {
+ dev_err(idt82p33->dev,
+ "Channel_init failed in %s with err %d!\n",
+ __func__, err);
+ return err;
+ }
+
+ channel->idt82p33 = idt82p33;
+
+ idt82p33_caps_init(&channel->caps);
+ snprintf(channel->caps.name, sizeof(channel->caps.name),
+ "IDT 82P33 PLL%u", index);
+
+ channel->ptp_clock = ptp_clock_register(&channel->caps, NULL);
+
+ if (IS_ERR(channel->ptp_clock)) {
+ err = PTR_ERR(channel->ptp_clock);
+ channel->ptp_clock = NULL;
+ return err;
+ }
+
+ if (!channel->ptp_clock)
+ return -ENOTSUPP;
+
+ err = idt82p33_dpll_set_mode(channel, PLL_MODE_DCO);
+ if (err) {
+ dev_err(idt82p33->dev,
+ "Dpll_set_mode failed in %s with err %d!\n",
+ __func__, err);
+ return err;
+ }
+
+ err = idt82p33_enable_tod(channel);
+ if (err) {
+ dev_err(idt82p33->dev,
+ "Enable_tod failed in %s with err %d!\n",
+ __func__, err);
+ return err;
+ }
+
+ dev_info(idt82p33->dev, "PLL%d registered as ptp%d\n",
+ index, channel->ptp_clock->index);
+
+ return 0;
+}
+
+static int idt82p33_load_firmware(struct idt82p33 *idt82p33)
+{
+ const struct firmware *fw;
+ struct idt82p33_fwrc *rec;
+ u8 loaddr, page, val;
+ int err;
+ s32 len;
+
+ dev_dbg(idt82p33->dev, "requesting firmware '%s'\n", FW_FILENAME);
+
+ err = request_firmware(&fw, FW_FILENAME, idt82p33->dev);
+
+ if (err) {
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n", __func__, err);
+ return err;
+ }
+
+ dev_dbg(idt82p33->dev, "firmware size %zu bytes\n", fw->size);
+
+ rec = (struct idt82p33_fwrc *) fw->data;
+
+ for (len = fw->size; len > 0; len -= sizeof(*rec)) {
+
+ if (rec->reserved) {
+ dev_err(idt82p33->dev,
+ "bad firmware, reserved field non-zero\n");
+ err = -EINVAL;
+ } else {
+ val = rec->value;
+ loaddr = rec->loaddr;
+ page = rec->hiaddr;
+
+ rec++;
+
+ err = idt82p33_check_and_set_masks(idt82p33, page,
+ loaddr, val);
+ }
+
+ if (err == 0) {
+ /* Page size 128, last 4 bytes of page skipped */
+ if (loaddr > 0x7b)
+ continue;
+
+ err = idt82p33_write(idt82p33, REG_ADDR(page, loaddr),
+ &val, sizeof(val));
+ }
+
+ if (err)
+ goto out;
+ }
+
+ idt82p33_display_masks(idt82p33);
+out:
+ release_firmware(fw);
+ return err;
+}
+
+
+static int idt82p33_probe(struct platform_device *pdev)
+{
+ struct rsmu_ddata *ddata = dev_get_drvdata(pdev->dev.parent);
+ struct idt82p33 *idt82p33;
+ int err;
+ u8 i;
+
+ idt82p33 = devm_kzalloc(&pdev->dev,
+ sizeof(struct idt82p33), GFP_KERNEL);
+ if (!idt82p33)
+ return -ENOMEM;
+
+ idt82p33->dev = &pdev->dev;
+ idt82p33->mfd = pdev->dev.parent;
+ idt82p33->lock = &ddata->lock;
+ idt82p33->regmap = ddata->regmap;
+ idt82p33->tod_write_overhead_ns = 0;
+ idt82p33->calculate_overhead_flag = 0;
+ idt82p33->pll_mask = DEFAULT_PLL_MASK;
+ idt82p33->channel[0].output_mask = DEFAULT_OUTPUT_MASK_PLL0;
+ idt82p33->channel[1].output_mask = DEFAULT_OUTPUT_MASK_PLL1;
+
+ mutex_lock(idt82p33->lock);
+
+ err = idt82p33_load_firmware(idt82p33);
+
+ if (err)
+ dev_warn(idt82p33->dev,
+ "loading firmware failed with %d\n", err);
+
+ if (idt82p33->pll_mask) {
+ for (i = 0; i < MAX_PHC_PLL; i++) {
+ if (idt82p33->pll_mask & (1 << i)) {
+ err = idt82p33_enable_channel(idt82p33, i);
+ if (err) {
+ dev_err(idt82p33->dev,
+ "Failed in %s with err %d!\n",
+ __func__, err);
+ break;
+ }
+ }
+ }
+ } else {
+ dev_err(idt82p33->dev,
+ "no PLLs flagged as PHCs, nothing to do\n");
+ err = -ENODEV;
+ }
+
+ mutex_unlock(idt82p33->lock);
+
+ if (err) {
+ idt82p33_ptp_clock_unregister_all(idt82p33);
+ return err;
+ }
+
+ platform_set_drvdata(pdev, idt82p33);
+
+ return 0;
+}
+
+static int idt82p33_remove(struct platform_device *pdev)
+{
+ struct idt82p33 *idt82p33 = platform_get_drvdata(pdev);
+
+ idt82p33_ptp_clock_unregister_all(idt82p33);
+
+ return 0;
+}
+
+static struct platform_driver idt82p33_driver = {
+ .driver = {
+ .name = "82p33x1x-phc",
+ },
+ .probe = idt82p33_probe,
+ .remove = idt82p33_remove,
+};
+
+module_platform_driver(idt82p33_driver);
diff --git a/drivers/ptp/ptp_idt82p33.h b/drivers/ptp/ptp_idt82p33.h
new file mode 100644
index 000000000000..0ea1c35c0f9f
--- /dev/null
+++ b/drivers/ptp/ptp_idt82p33.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * PTP hardware clock driver for the IDT 82P33XXX family of clocks.
+ *
+ * Copyright (C) 2019 Integrated Device Technology, Inc., a Renesas Company.
+ */
+#ifndef PTP_IDT82P33_H
+#define PTP_IDT82P33_H
+
+#include <linux/ktime.h>
+#include <linux/mfd/idt82p33_reg.h>
+#include <linux/regmap.h>
+
+#define FW_FILENAME "idt82p33xxx.bin"
+#define MAX_PHC_PLL (2)
+#define TOD_BYTE_COUNT (10)
+#define DCO_MAX_PPB (92000)
+#define MAX_MEASURMENT_COUNT (5)
+#define SNAP_THRESHOLD_NS (10000)
+#define IMMEDIATE_SNAP_THRESHOLD_NS (50000)
+#define DDCO_THRESHOLD_NS (5)
+#define IDT82P33_MAX_WRITE_COUNT (512)
+#define PEROUT_ENABLE_OUTPUT_MASK (0xdeadbeef)
+
+#define PLLMASK_ADDR_HI 0xFF
+#define PLLMASK_ADDR_LO 0xA5
+
+#define PLL0_OUTMASK_ADDR_HI 0xFF
+#define PLL0_OUTMASK_ADDR_LO 0xB0
+
+#define PLL1_OUTMASK_ADDR_HI 0xFF
+#define PLL1_OUTMASK_ADDR_LO 0xB2
+
+#define PLL2_OUTMASK_ADDR_HI 0xFF
+#define PLL2_OUTMASK_ADDR_LO 0xB4
+
+#define PLL3_OUTMASK_ADDR_HI 0xFF
+#define PLL3_OUTMASK_ADDR_LO 0xB6
+
+#define DEFAULT_PLL_MASK (0x01)
+#define DEFAULT_OUTPUT_MASK_PLL0 (0xc0)
+#define DEFAULT_OUTPUT_MASK_PLL1 DEFAULT_OUTPUT_MASK_PLL0
+
+/**
+ * @brief Maximum absolute value for write phase offset in femtoseconds
+ */
+#define WRITE_PHASE_OFFSET_LIMIT (20000052084ll)
+
+/** @brief Phase offset resolution
+ *
+ * DPLL phase offset = 10^15 fs / ( System Clock * 2^13)
+ * = 10^15 fs / ( 1638400000 * 2^23)
+ * = 74.5058059692382 fs
+ */
+#define IDT_T0DPLL_PHASE_RESOL 74506
+
+/* PTP Hardware Clock interface */
+struct idt82p33_channel {
+ struct ptp_clock_info caps;
+ struct ptp_clock *ptp_clock;
+ struct idt82p33 *idt82p33;
+ enum pll_mode pll_mode;
+ s32 current_freq_ppb;
+ u8 output_mask;
+ u16 dpll_tod_cnfg;
+ u16 dpll_tod_trigger;
+ u16 dpll_tod_sts;
+ u16 dpll_mode_cnfg;
+ u16 dpll_freq_cnfg;
+ u16 dpll_phase_cnfg;
+ u16 dpll_sync_cnfg;
+ u16 dpll_input_mode_cnfg;
+};
+
+struct idt82p33 {
+ struct idt82p33_channel channel[MAX_PHC_PLL];
+ struct device *dev;
+ u8 pll_mask;
+ /* Mutex to protect operations from being interrupted */
+ struct mutex *lock;
+ struct regmap *regmap;
+ struct device *mfd;
+ /* Overhead calculation for adjtime */
+ ktime_t start_time;
+ int calculate_overhead_flag;
+ s64 tod_write_overhead_ns;
+};
+
+/* firmware interface */
+struct idt82p33_fwrc {
+ u8 hiaddr;
+ u8 loaddr;
+ u8 value;
+ u8 reserved;
+} __packed;
+
+#endif /* PTP_IDT82P33_H */
diff --git a/drivers/ptp/ptp_ines.c b/drivers/ptp/ptp_ines.c
index dfda54cbd866..61f47fb9d997 100644
--- a/drivers/ptp/ptp_ines.c
+++ b/drivers/ptp/ptp_ines.c
@@ -93,9 +93,6 @@ MODULE_LICENSE("GPL");
#define TC_E2E_PTP_V2 2
#define TC_P2P_PTP_V2 3
-#define OFF_PTP_CLOCK_ID 20
-#define OFF_PTP_PORT_NUM 28
-
#define PHY_SPEED_10 0
#define PHY_SPEED_100 1
#define PHY_SPEED_1000 2
@@ -111,11 +108,6 @@ MODULE_LICENSE("GPL");
#define MESSAGE_TYPE_P_DELAY_RESP 3
#define MESSAGE_TYPE_DELAY_REQ 4
-#define SYNC 0x0
-#define DELAY_REQ 0x1
-#define PDELAY_REQ 0x2
-#define PDELAY_RESP 0x3
-
static LIST_HEAD(ines_clocks);
static DEFINE_MUTEX(ines_clocks_lock);
@@ -346,10 +338,6 @@ static int ines_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr)
if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
return -EFAULT;
- /* reserved for future extensions */
- if (cfg.flags)
- return -EINVAL;
-
switch (cfg.tx_type) {
case HWTSTAMP_TX_OFF:
ts_stat_tx = 0;
@@ -400,8 +388,8 @@ static int ines_hwtstamp(struct mii_timestamper *mii_ts, struct ifreq *ifr)
ines_write32(port, ts_stat_rx, ts_stat_rx);
ines_write32(port, ts_stat_tx, ts_stat_tx);
- port->rxts_enabled = ts_stat_rx == TS_ENABLE ? true : false;
- port->txts_enabled = ts_stat_tx == TS_ENABLE ? true : false;
+ port->rxts_enabled = ts_stat_rx == TS_ENABLE;
+ port->txts_enabled = ts_stat_tx == TS_ENABLE;
spin_unlock_irqrestore(&port->lock, flags);
@@ -443,57 +431,41 @@ static void ines_link_state(struct mii_timestamper *mii_ts,
static bool ines_match(struct sk_buff *skb, unsigned int ptp_class,
struct ines_timestamp *ts, struct device *dev)
{
- u8 *msgtype, *data = skb_mac_header(skb);
- unsigned int offset = 0;
- __be16 *portn, *seqid;
- __be64 *clkid;
+ struct ptp_header *hdr;
+ u16 portn, seqid;
+ u8 msgtype;
+ u64 clkid;
if (unlikely(ptp_class & PTP_CLASS_V1))
return false;
- if (ptp_class & PTP_CLASS_VLAN)
- offset += VLAN_HLEN;
-
- switch (ptp_class & PTP_CLASS_PMASK) {
- case PTP_CLASS_IPV4:
- offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN;
- break;
- case PTP_CLASS_IPV6:
- offset += ETH_HLEN + IP6_HLEN + UDP_HLEN;
- break;
- case PTP_CLASS_L2:
- offset += ETH_HLEN;
- break;
- default:
- return false;
- }
-
- if (skb->len + ETH_HLEN < offset + OFF_PTP_SEQUENCE_ID + sizeof(*seqid))
+ hdr = ptp_parse_header(skb, ptp_class);
+ if (!hdr)
return false;
- msgtype = data + offset;
- clkid = (__be64 *)(data + offset + OFF_PTP_CLOCK_ID);
- portn = (__be16 *)(data + offset + OFF_PTP_PORT_NUM);
- seqid = (__be16 *)(data + offset + OFF_PTP_SEQUENCE_ID);
+ msgtype = ptp_get_msgtype(hdr, ptp_class);
+ clkid = be64_to_cpup((__be64 *)&hdr->source_port_identity.clock_identity.id[0]);
+ portn = be16_to_cpu(hdr->source_port_identity.port_number);
+ seqid = be16_to_cpu(hdr->sequence_id);
- if (tag_to_msgtype(ts->tag & 0x7) != (*msgtype & 0xf)) {
+ if (tag_to_msgtype(ts->tag & 0x7) != msgtype) {
dev_dbg(dev, "msgtype mismatch ts %hhu != skb %hhu\n",
- tag_to_msgtype(ts->tag & 0x7), *msgtype & 0xf);
+ tag_to_msgtype(ts->tag & 0x7), msgtype);
return false;
}
- if (cpu_to_be64(ts->clkid) != *clkid) {
+ if (ts->clkid != clkid) {
dev_dbg(dev, "clkid mismatch ts %llx != skb %llx\n",
- cpu_to_be64(ts->clkid), *clkid);
+ ts->clkid, clkid);
return false;
}
- if (ts->portnum != ntohs(*portn)) {
+ if (ts->portnum != portn) {
dev_dbg(dev, "portn mismatch ts %hu != skb %hu\n",
- ts->portnum, ntohs(*portn));
+ ts->portnum, portn);
return false;
}
- if (ts->seqid != ntohs(*seqid)) {
+ if (ts->seqid != seqid) {
dev_dbg(dev, "seqid mismatch ts %hu != skb %hu\n",
- ts->seqid, ntohs(*seqid));
+ ts->seqid, seqid);
return false;
}
@@ -663,8 +635,7 @@ static void ines_txtstamp(struct mii_timestamper *mii_ts,
spin_unlock_irqrestore(&port->lock, flags);
- if (old_skb)
- kfree_skb(old_skb);
+ kfree_skb(old_skb);
schedule_delayed_work(&port->ts_work, 1);
}
@@ -694,37 +665,18 @@ static void ines_txtstamp_work(struct work_struct *work)
static bool is_sync_pdelay_resp(struct sk_buff *skb, int type)
{
- u8 *data = skb->data, *msgtype;
- unsigned int offset = 0;
-
- if (type & PTP_CLASS_VLAN)
- offset += VLAN_HLEN;
+ struct ptp_header *hdr;
+ u8 msgtype;
- switch (type & PTP_CLASS_PMASK) {
- case PTP_CLASS_IPV4:
- offset += ETH_HLEN + IPV4_HLEN(data + offset) + UDP_HLEN;
- break;
- case PTP_CLASS_IPV6:
- offset += ETH_HLEN + IP6_HLEN + UDP_HLEN;
- break;
- case PTP_CLASS_L2:
- offset += ETH_HLEN;
- break;
- default:
- return 0;
- }
-
- if (type & PTP_CLASS_V1)
- offset += OFF_PTP_CONTROL;
-
- if (skb->len < offset + 1)
- return 0;
+ hdr = ptp_parse_header(skb, type);
+ if (!hdr)
+ return false;
- msgtype = data + offset;
+ msgtype = ptp_get_msgtype(hdr, type);
- switch ((*msgtype & 0xf)) {
- case SYNC:
- case PDELAY_RESP:
+ switch (msgtype) {
+ case PTP_MSGTYPE_SYNC:
+ case PTP_MSGTYPE_PDELAY_RESP:
return true;
default:
return false;
@@ -735,13 +687,13 @@ static u8 tag_to_msgtype(u8 tag)
{
switch (tag) {
case MESSAGE_TYPE_SYNC:
- return SYNC;
+ return PTP_MSGTYPE_SYNC;
case MESSAGE_TYPE_P_DELAY_REQ:
- return PDELAY_REQ;
+ return PTP_MSGTYPE_PDELAY_REQ;
case MESSAGE_TYPE_P_DELAY_RESP:
- return PDELAY_RESP;
+ return PTP_MSGTYPE_PDELAY_RESP;
case MESSAGE_TYPE_DELAY_REQ:
- return DELAY_REQ;
+ return PTP_MSGTYPE_DELAY_REQ;
}
return 0xf;
}
@@ -783,16 +735,10 @@ static struct mii_timestamping_ctrl ines_ctrl = {
static int ines_ptp_ctrl_probe(struct platform_device *pld)
{
struct ines_clock *clock;
- struct resource *res;
void __iomem *addr;
int err = 0;
- res = platform_get_resource(pld, IORESOURCE_MEM, 0);
- if (!res) {
- dev_err(&pld->dev, "missing memory resource\n");
- return -EINVAL;
- }
- addr = devm_ioremap_resource(&pld->dev, res);
+ addr = devm_platform_ioremap_resource(pld, 0);
if (IS_ERR(addr)) {
err = PTR_ERR(addr);
goto out;
diff --git a/drivers/ptp/ptp_kvm_arm.c b/drivers/ptp/ptp_kvm_arm.c
new file mode 100644
index 000000000000..b7d28c8dfb84
--- /dev/null
+++ b/drivers/ptp/ptp_kvm_arm.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Virtual PTP 1588 clock for use with KVM guests
+ * Copyright (C) 2019 ARM Ltd.
+ * All Rights Reserved
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/ptp_kvm.h>
+
+#include <asm/arch_timer.h>
+#include <asm/hypervisor.h>
+
+int kvm_arch_ptp_init(void)
+{
+ int ret;
+
+ ret = kvm_arm_hyp_service_available(ARM_SMCCC_KVM_FUNC_PTP);
+ if (ret <= 0)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+int kvm_arch_ptp_get_clock(struct timespec64 *ts)
+{
+ return kvm_arch_ptp_get_crosststamp(NULL, ts, NULL);
+}
diff --git a/drivers/ptp/ptp_kvm.c b/drivers/ptp/ptp_kvm_common.c
index fc7d0b77e118..fcae32f56f25 100644
--- a/drivers/ptp/ptp_kvm.c
+++ b/drivers/ptp/ptp_kvm_common.c
@@ -8,11 +8,11 @@
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/slab.h>
#include <linux/module.h>
+#include <linux/ptp_kvm.h>
#include <uapi/linux/kvm_para.h>
#include <asm/kvm_para.h>
-#include <asm/pvclock.h>
-#include <asm/kvmclock.h>
#include <uapi/asm/kvm_para.h>
#include <linux/ptp_clock_kernel.h>
@@ -22,58 +22,31 @@ struct kvm_ptp_clock {
struct ptp_clock_info caps;
};
-DEFINE_SPINLOCK(kvm_ptp_lock);
-
-static struct pvclock_vsyscall_time_info *hv_clock;
-
-static struct kvm_clock_pairing clock_pair;
-static phys_addr_t clock_pair_gpa;
+static DEFINE_SPINLOCK(kvm_ptp_lock);
static int ptp_kvm_get_time_fn(ktime_t *device_time,
struct system_counterval_t *system_counter,
void *ctx)
{
- unsigned long ret;
+ long ret;
+ u64 cycle;
struct timespec64 tspec;
- unsigned version;
- int cpu;
- struct pvclock_vcpu_time_info *src;
+ struct clocksource *cs;
spin_lock(&kvm_ptp_lock);
preempt_disable_notrace();
- cpu = smp_processor_id();
- src = &hv_clock[cpu].pvti;
-
- do {
- /*
- * We are using a TSC value read in the hosts
- * kvm_hc_clock_pairing handling.
- * So any changes to tsc_to_system_mul
- * and tsc_shift or any other pvclock
- * data invalidate that measurement.
- */
- version = pvclock_read_begin(src);
-
- ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING,
- clock_pair_gpa,
- KVM_CLOCK_PAIRING_WALLCLOCK);
- if (ret != 0) {
- pr_err_ratelimited("clock pairing hypercall ret %lu\n", ret);
- spin_unlock(&kvm_ptp_lock);
- preempt_enable_notrace();
- return -EOPNOTSUPP;
- }
-
- tspec.tv_sec = clock_pair.sec;
- tspec.tv_nsec = clock_pair.nsec;
- ret = __pvclock_read_cycles(src, clock_pair.tsc);
- } while (pvclock_read_retry(src, version));
+ ret = kvm_arch_ptp_get_crosststamp(&cycle, &tspec, &cs);
+ if (ret) {
+ spin_unlock(&kvm_ptp_lock);
+ preempt_enable_notrace();
+ return ret;
+ }
preempt_enable_notrace();
- system_counter->cycles = ret;
- system_counter->cs = &kvm_clock;
+ system_counter->cycles = cycle;
+ system_counter->cs = cs;
*device_time = timespec64_to_ktime(tspec);
@@ -111,22 +84,17 @@ static int ptp_kvm_settime(struct ptp_clock_info *ptp,
static int ptp_kvm_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
- unsigned long ret;
+ long ret;
struct timespec64 tspec;
spin_lock(&kvm_ptp_lock);
- ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING,
- clock_pair_gpa,
- KVM_CLOCK_PAIRING_WALLCLOCK);
- if (ret != 0) {
- pr_err_ratelimited("clock offset hypercall ret %lu\n", ret);
+ ret = kvm_arch_ptp_get_clock(&tspec);
+ if (ret) {
spin_unlock(&kvm_ptp_lock);
- return -EOPNOTSUPP;
+ return ret;
}
- tspec.tv_sec = clock_pair.sec;
- tspec.tv_nsec = clock_pair.nsec;
spin_unlock(&kvm_ptp_lock);
memcpy(ts, &tspec, sizeof(struct timespec64));
@@ -168,19 +136,12 @@ static int __init ptp_kvm_init(void)
{
long ret;
- if (!kvm_para_available())
- return -ENODEV;
-
- clock_pair_gpa = slow_virt_to_phys(&clock_pair);
- hv_clock = pvclock_get_pvti_cpu0_va();
-
- if (!hv_clock)
- return -ENODEV;
-
- ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING, clock_pair_gpa,
- KVM_CLOCK_PAIRING_WALLCLOCK);
- if (ret == -KVM_ENOSYS || ret == -KVM_EOPNOTSUPP)
- return -ENODEV;
+ ret = kvm_arch_ptp_init();
+ if (ret) {
+ if (ret != -EOPNOTSUPP)
+ pr_err("fail to initialize ptp_kvm");
+ return ret;
+ }
kvm_ptp_clock.caps = ptp_kvm_caps;
diff --git a/drivers/ptp/ptp_kvm_x86.c b/drivers/ptp/ptp_kvm_x86.c
new file mode 100644
index 000000000000..4991054a2135
--- /dev/null
+++ b/drivers/ptp/ptp_kvm_x86.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Virtual PTP 1588 clock for use with KVM guests
+ *
+ * Copyright (C) 2017 Red Hat Inc.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <asm/pvclock.h>
+#include <asm/kvmclock.h>
+#include <linux/module.h>
+#include <uapi/asm/kvm_para.h>
+#include <uapi/linux/kvm_para.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/ptp_kvm.h>
+
+static phys_addr_t clock_pair_gpa;
+static struct kvm_clock_pairing clock_pair;
+
+int kvm_arch_ptp_init(void)
+{
+ long ret;
+
+ if (!kvm_para_available())
+ return -ENODEV;
+
+ clock_pair_gpa = slow_virt_to_phys(&clock_pair);
+ if (!pvclock_get_pvti_cpu0_va())
+ return -ENODEV;
+
+ ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING, clock_pair_gpa,
+ KVM_CLOCK_PAIRING_WALLCLOCK);
+ if (ret == -KVM_ENOSYS)
+ return -ENODEV;
+
+ return ret;
+}
+
+int kvm_arch_ptp_get_clock(struct timespec64 *ts)
+{
+ long ret;
+
+ ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING,
+ clock_pair_gpa,
+ KVM_CLOCK_PAIRING_WALLCLOCK);
+ if (ret != 0) {
+ pr_err_ratelimited("clock offset hypercall ret %lu\n", ret);
+ return -EOPNOTSUPP;
+ }
+
+ ts->tv_sec = clock_pair.sec;
+ ts->tv_nsec = clock_pair.nsec;
+
+ return 0;
+}
+
+int kvm_arch_ptp_get_crosststamp(u64 *cycle, struct timespec64 *tspec,
+ struct clocksource **cs)
+{
+ struct pvclock_vcpu_time_info *src;
+ unsigned int version;
+ long ret;
+
+ src = this_cpu_pvti();
+
+ do {
+ /*
+ * We are using a TSC value read in the hosts
+ * kvm_hc_clock_pairing handling.
+ * So any changes to tsc_to_system_mul
+ * and tsc_shift or any other pvclock
+ * data invalidate that measurement.
+ */
+ version = pvclock_read_begin(src);
+
+ ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING,
+ clock_pair_gpa,
+ KVM_CLOCK_PAIRING_WALLCLOCK);
+ if (ret != 0) {
+ pr_err_ratelimited("clock pairing hypercall ret %lu\n", ret);
+ return -EOPNOTSUPP;
+ }
+ tspec->tv_sec = clock_pair.sec;
+ tspec->tv_nsec = clock_pair.nsec;
+ *cycle = __pvclock_read_cycles(src, clock_pair.tsc);
+ } while (pvclock_read_retry(src, version));
+
+ *cs = &kvm_clock;
+
+ return 0;
+}
diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c
new file mode 100644
index 000000000000..a48d9b7d2921
--- /dev/null
+++ b/drivers/ptp/ptp_ocp.c
@@ -0,0 +1,3885 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2020 Facebook */
+
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/serial_8250.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/i2c-xiic.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/xilinx_spi.h>
+#include <net/devlink.h>
+#include <linux/i2c.h>
+#include <linux/mtd/mtd.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/crc16.h>
+
+#define PCI_VENDOR_ID_FACEBOOK 0x1d9b
+#define PCI_DEVICE_ID_FACEBOOK_TIMECARD 0x0400
+
+#define PCI_VENDOR_ID_CELESTICA 0x18d4
+#define PCI_DEVICE_ID_CELESTICA_TIMECARD 0x1008
+
+static struct class timecard_class = {
+ .owner = THIS_MODULE,
+ .name = "timecard",
+};
+
+struct ocp_reg {
+ u32 ctrl;
+ u32 status;
+ u32 select;
+ u32 version;
+ u32 time_ns;
+ u32 time_sec;
+ u32 __pad0[2];
+ u32 adjust_ns;
+ u32 adjust_sec;
+ u32 __pad1[2];
+ u32 offset_ns;
+ u32 offset_window_ns;
+ u32 __pad2[2];
+ u32 drift_ns;
+ u32 drift_window_ns;
+ u32 __pad3[6];
+ u32 servo_offset_p;
+ u32 servo_offset_i;
+ u32 servo_drift_p;
+ u32 servo_drift_i;
+ u32 status_offset;
+ u32 status_drift;
+};
+
+#define OCP_CTRL_ENABLE BIT(0)
+#define OCP_CTRL_ADJUST_TIME BIT(1)
+#define OCP_CTRL_ADJUST_OFFSET BIT(2)
+#define OCP_CTRL_ADJUST_DRIFT BIT(3)
+#define OCP_CTRL_ADJUST_SERVO BIT(8)
+#define OCP_CTRL_READ_TIME_REQ BIT(30)
+#define OCP_CTRL_READ_TIME_DONE BIT(31)
+
+#define OCP_STATUS_IN_SYNC BIT(0)
+#define OCP_STATUS_IN_HOLDOVER BIT(1)
+
+#define OCP_SELECT_CLK_NONE 0
+#define OCP_SELECT_CLK_REG 0xfe
+
+struct tod_reg {
+ u32 ctrl;
+ u32 status;
+ u32 uart_polarity;
+ u32 version;
+ u32 adj_sec;
+ u32 __pad0[3];
+ u32 uart_baud;
+ u32 __pad1[3];
+ u32 utc_status;
+ u32 leap;
+};
+
+#define TOD_CTRL_PROTOCOL BIT(28)
+#define TOD_CTRL_DISABLE_FMT_A BIT(17)
+#define TOD_CTRL_DISABLE_FMT_B BIT(16)
+#define TOD_CTRL_ENABLE BIT(0)
+#define TOD_CTRL_GNSS_MASK GENMASK(3, 0)
+#define TOD_CTRL_GNSS_SHIFT 24
+
+#define TOD_STATUS_UTC_MASK GENMASK(7, 0)
+#define TOD_STATUS_UTC_VALID BIT(8)
+#define TOD_STATUS_LEAP_ANNOUNCE BIT(12)
+#define TOD_STATUS_LEAP_VALID BIT(16)
+
+struct ts_reg {
+ u32 enable;
+ u32 error;
+ u32 polarity;
+ u32 version;
+ u32 __pad0[4];
+ u32 cable_delay;
+ u32 __pad1[3];
+ u32 intr;
+ u32 intr_mask;
+ u32 event_count;
+ u32 __pad2[1];
+ u32 ts_count;
+ u32 time_ns;
+ u32 time_sec;
+ u32 data_width;
+ u32 data;
+};
+
+struct pps_reg {
+ u32 ctrl;
+ u32 status;
+ u32 __pad0[6];
+ u32 cable_delay;
+};
+
+#define PPS_STATUS_FILTER_ERR BIT(0)
+#define PPS_STATUS_SUPERV_ERR BIT(1)
+
+struct img_reg {
+ u32 version;
+};
+
+struct gpio_reg {
+ u32 gpio1;
+ u32 __pad0;
+ u32 gpio2;
+ u32 __pad1;
+};
+
+struct irig_master_reg {
+ u32 ctrl;
+ u32 status;
+ u32 __pad0;
+ u32 version;
+ u32 adj_sec;
+ u32 mode_ctrl;
+};
+
+#define IRIG_M_CTRL_ENABLE BIT(0)
+
+struct irig_slave_reg {
+ u32 ctrl;
+ u32 status;
+ u32 __pad0;
+ u32 version;
+ u32 adj_sec;
+ u32 mode_ctrl;
+};
+
+#define IRIG_S_CTRL_ENABLE BIT(0)
+
+struct dcf_master_reg {
+ u32 ctrl;
+ u32 status;
+ u32 __pad0;
+ u32 version;
+ u32 adj_sec;
+};
+
+#define DCF_M_CTRL_ENABLE BIT(0)
+
+struct dcf_slave_reg {
+ u32 ctrl;
+ u32 status;
+ u32 __pad0;
+ u32 version;
+ u32 adj_sec;
+};
+
+#define DCF_S_CTRL_ENABLE BIT(0)
+
+struct signal_reg {
+ u32 enable;
+ u32 status;
+ u32 polarity;
+ u32 version;
+ u32 __pad0[4];
+ u32 cable_delay;
+ u32 __pad1[3];
+ u32 intr;
+ u32 intr_mask;
+ u32 __pad2[2];
+ u32 start_ns;
+ u32 start_sec;
+ u32 pulse_ns;
+ u32 pulse_sec;
+ u32 period_ns;
+ u32 period_sec;
+ u32 repeat_count;
+};
+
+struct frequency_reg {
+ u32 ctrl;
+ u32 status;
+};
+#define FREQ_STATUS_VALID BIT(31)
+#define FREQ_STATUS_ERROR BIT(30)
+#define FREQ_STATUS_OVERRUN BIT(29)
+#define FREQ_STATUS_MASK GENMASK(23, 0)
+
+struct ptp_ocp_flash_info {
+ const char *name;
+ int pci_offset;
+ int data_size;
+ void *data;
+};
+
+struct ptp_ocp_firmware_header {
+ char magic[4];
+ __be16 pci_vendor_id;
+ __be16 pci_device_id;
+ __be32 image_size;
+ __be16 hw_revision;
+ __be16 crc;
+};
+
+#define OCP_FIRMWARE_MAGIC_HEADER "OCPC"
+
+struct ptp_ocp_i2c_info {
+ const char *name;
+ unsigned long fixed_rate;
+ size_t data_size;
+ void *data;
+};
+
+struct ptp_ocp_ext_info {
+ int index;
+ irqreturn_t (*irq_fcn)(int irq, void *priv);
+ int (*enable)(void *priv, u32 req, bool enable);
+};
+
+struct ptp_ocp_ext_src {
+ void __iomem *mem;
+ struct ptp_ocp *bp;
+ struct ptp_ocp_ext_info *info;
+ int irq_vec;
+};
+
+enum ptp_ocp_sma_mode {
+ SMA_MODE_IN,
+ SMA_MODE_OUT,
+};
+
+struct ptp_ocp_sma_connector {
+ enum ptp_ocp_sma_mode mode;
+ bool fixed_fcn;
+ bool fixed_dir;
+ bool disabled;
+ u8 default_fcn;
+};
+
+struct ocp_attr_group {
+ u64 cap;
+ const struct attribute_group *group;
+};
+
+#define OCP_CAP_BASIC BIT(0)
+#define OCP_CAP_SIGNAL BIT(1)
+#define OCP_CAP_FREQ BIT(2)
+
+struct ptp_ocp_signal {
+ ktime_t period;
+ ktime_t pulse;
+ ktime_t phase;
+ ktime_t start;
+ int duty;
+ bool polarity;
+ bool running;
+};
+
+#define OCP_BOARD_ID_LEN 13
+#define OCP_SERIAL_LEN 6
+
+struct ptp_ocp {
+ struct pci_dev *pdev;
+ struct device dev;
+ spinlock_t lock;
+ struct ocp_reg __iomem *reg;
+ struct tod_reg __iomem *tod;
+ struct pps_reg __iomem *pps_to_ext;
+ struct pps_reg __iomem *pps_to_clk;
+ struct gpio_reg __iomem *pps_select;
+ struct gpio_reg __iomem *sma_map1;
+ struct gpio_reg __iomem *sma_map2;
+ struct irig_master_reg __iomem *irig_out;
+ struct irig_slave_reg __iomem *irig_in;
+ struct dcf_master_reg __iomem *dcf_out;
+ struct dcf_slave_reg __iomem *dcf_in;
+ struct tod_reg __iomem *nmea_out;
+ struct frequency_reg __iomem *freq_in[4];
+ struct ptp_ocp_ext_src *signal_out[4];
+ struct ptp_ocp_ext_src *pps;
+ struct ptp_ocp_ext_src *ts0;
+ struct ptp_ocp_ext_src *ts1;
+ struct ptp_ocp_ext_src *ts2;
+ struct ptp_ocp_ext_src *ts3;
+ struct ptp_ocp_ext_src *ts4;
+ struct img_reg __iomem *image;
+ struct ptp_clock *ptp;
+ struct ptp_clock_info ptp_info;
+ struct platform_device *i2c_ctrl;
+ struct platform_device *spi_flash;
+ struct clk_hw *i2c_clk;
+ struct timer_list watchdog;
+ const struct attribute_group **attr_group;
+ const struct ptp_ocp_eeprom_map *eeprom_map;
+ struct dentry *debug_root;
+ time64_t gnss_lost;
+ int id;
+ int n_irqs;
+ int gnss_port;
+ int gnss2_port;
+ int mac_port; /* miniature atomic clock */
+ int nmea_port;
+ bool fw_loader;
+ u8 fw_tag;
+ u16 fw_version;
+ u8 board_id[OCP_BOARD_ID_LEN];
+ u8 serial[OCP_SERIAL_LEN];
+ bool has_eeprom_data;
+ u32 pps_req_map;
+ int flash_start;
+ u32 utc_tai_offset;
+ u32 ts_window_adjust;
+ u64 fw_cap;
+ struct ptp_ocp_signal signal[4];
+ struct ptp_ocp_sma_connector sma[4];
+ const struct ocp_sma_op *sma_op;
+};
+
+#define OCP_REQ_TIMESTAMP BIT(0)
+#define OCP_REQ_PPS BIT(1)
+
+struct ocp_resource {
+ unsigned long offset;
+ int size;
+ int irq_vec;
+ int (*setup)(struct ptp_ocp *bp, struct ocp_resource *r);
+ void *extra;
+ unsigned long bp_offset;
+ const char * const name;
+};
+
+static int ptp_ocp_register_mem(struct ptp_ocp *bp, struct ocp_resource *r);
+static int ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_resource *r);
+static int ptp_ocp_register_spi(struct ptp_ocp *bp, struct ocp_resource *r);
+static int ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r);
+static int ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r);
+static int ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r);
+static irqreturn_t ptp_ocp_ts_irq(int irq, void *priv);
+static irqreturn_t ptp_ocp_signal_irq(int irq, void *priv);
+static int ptp_ocp_ts_enable(void *priv, u32 req, bool enable);
+static int ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen,
+ struct ptp_perout_request *req);
+static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable);
+static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr);
+
+static const struct ocp_attr_group fb_timecard_groups[];
+
+struct ptp_ocp_eeprom_map {
+ u16 off;
+ u16 len;
+ u32 bp_offset;
+ const void * const tag;
+};
+
+#define EEPROM_ENTRY(addr, member) \
+ .off = addr, \
+ .len = sizeof_field(struct ptp_ocp, member), \
+ .bp_offset = offsetof(struct ptp_ocp, member)
+
+#define BP_MAP_ENTRY_ADDR(bp, map) ({ \
+ (void *)((uintptr_t)(bp) + (map)->bp_offset); \
+})
+
+static struct ptp_ocp_eeprom_map fb_eeprom_map[] = {
+ { EEPROM_ENTRY(0x43, board_id) },
+ { EEPROM_ENTRY(0x00, serial), .tag = "mac" },
+ { }
+};
+
+#define bp_assign_entry(bp, res, val) ({ \
+ uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset; \
+ *(typeof(val) *)addr = val; \
+})
+
+#define OCP_RES_LOCATION(member) \
+ .name = #member, .bp_offset = offsetof(struct ptp_ocp, member)
+
+#define OCP_MEM_RESOURCE(member) \
+ OCP_RES_LOCATION(member), .setup = ptp_ocp_register_mem
+
+#define OCP_SERIAL_RESOURCE(member) \
+ OCP_RES_LOCATION(member), .setup = ptp_ocp_register_serial
+
+#define OCP_I2C_RESOURCE(member) \
+ OCP_RES_LOCATION(member), .setup = ptp_ocp_register_i2c
+
+#define OCP_SPI_RESOURCE(member) \
+ OCP_RES_LOCATION(member), .setup = ptp_ocp_register_spi
+
+#define OCP_EXT_RESOURCE(member) \
+ OCP_RES_LOCATION(member), .setup = ptp_ocp_register_ext
+
+/* This is the MSI vector mapping used.
+ * 0: PPS (TS5)
+ * 1: TS0
+ * 2: TS1
+ * 3: GNSS1
+ * 4: GNSS2
+ * 5: MAC
+ * 6: TS2
+ * 7: I2C controller
+ * 8: HWICAP (notused)
+ * 9: SPI Flash
+ * 10: NMEA
+ * 11: Signal Generator 1
+ * 12: Signal Generator 2
+ * 13: Signal Generator 3
+ * 14: Signal Generator 4
+ * 15: TS3
+ * 16: TS4
+ */
+
+static struct ocp_resource ocp_fb_resource[] = {
+ {
+ OCP_MEM_RESOURCE(reg),
+ .offset = 0x01000000, .size = 0x10000,
+ },
+ {
+ OCP_EXT_RESOURCE(ts0),
+ .offset = 0x01010000, .size = 0x10000, .irq_vec = 1,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 0,
+ .irq_fcn = ptp_ocp_ts_irq,
+ .enable = ptp_ocp_ts_enable,
+ },
+ },
+ {
+ OCP_EXT_RESOURCE(ts1),
+ .offset = 0x01020000, .size = 0x10000, .irq_vec = 2,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 1,
+ .irq_fcn = ptp_ocp_ts_irq,
+ .enable = ptp_ocp_ts_enable,
+ },
+ },
+ {
+ OCP_EXT_RESOURCE(ts2),
+ .offset = 0x01060000, .size = 0x10000, .irq_vec = 6,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 2,
+ .irq_fcn = ptp_ocp_ts_irq,
+ .enable = ptp_ocp_ts_enable,
+ },
+ },
+ {
+ OCP_EXT_RESOURCE(ts3),
+ .offset = 0x01110000, .size = 0x10000, .irq_vec = 15,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 3,
+ .irq_fcn = ptp_ocp_ts_irq,
+ .enable = ptp_ocp_ts_enable,
+ },
+ },
+ {
+ OCP_EXT_RESOURCE(ts4),
+ .offset = 0x01120000, .size = 0x10000, .irq_vec = 16,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 4,
+ .irq_fcn = ptp_ocp_ts_irq,
+ .enable = ptp_ocp_ts_enable,
+ },
+ },
+ /* Timestamp for PHC and/or PPS generator */
+ {
+ OCP_EXT_RESOURCE(pps),
+ .offset = 0x010C0000, .size = 0x10000, .irq_vec = 0,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 5,
+ .irq_fcn = ptp_ocp_ts_irq,
+ .enable = ptp_ocp_ts_enable,
+ },
+ },
+ {
+ OCP_EXT_RESOURCE(signal_out[0]),
+ .offset = 0x010D0000, .size = 0x10000, .irq_vec = 11,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 1,
+ .irq_fcn = ptp_ocp_signal_irq,
+ .enable = ptp_ocp_signal_enable,
+ },
+ },
+ {
+ OCP_EXT_RESOURCE(signal_out[1]),
+ .offset = 0x010E0000, .size = 0x10000, .irq_vec = 12,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 2,
+ .irq_fcn = ptp_ocp_signal_irq,
+ .enable = ptp_ocp_signal_enable,
+ },
+ },
+ {
+ OCP_EXT_RESOURCE(signal_out[2]),
+ .offset = 0x010F0000, .size = 0x10000, .irq_vec = 13,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 3,
+ .irq_fcn = ptp_ocp_signal_irq,
+ .enable = ptp_ocp_signal_enable,
+ },
+ },
+ {
+ OCP_EXT_RESOURCE(signal_out[3]),
+ .offset = 0x01100000, .size = 0x10000, .irq_vec = 14,
+ .extra = &(struct ptp_ocp_ext_info) {
+ .index = 4,
+ .irq_fcn = ptp_ocp_signal_irq,
+ .enable = ptp_ocp_signal_enable,
+ },
+ },
+ {
+ OCP_MEM_RESOURCE(pps_to_ext),
+ .offset = 0x01030000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(pps_to_clk),
+ .offset = 0x01040000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(tod),
+ .offset = 0x01050000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(irig_in),
+ .offset = 0x01070000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(irig_out),
+ .offset = 0x01080000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(dcf_in),
+ .offset = 0x01090000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(dcf_out),
+ .offset = 0x010A0000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(nmea_out),
+ .offset = 0x010B0000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(image),
+ .offset = 0x00020000, .size = 0x1000,
+ },
+ {
+ OCP_MEM_RESOURCE(pps_select),
+ .offset = 0x00130000, .size = 0x1000,
+ },
+ {
+ OCP_MEM_RESOURCE(sma_map1),
+ .offset = 0x00140000, .size = 0x1000,
+ },
+ {
+ OCP_MEM_RESOURCE(sma_map2),
+ .offset = 0x00220000, .size = 0x1000,
+ },
+ {
+ OCP_I2C_RESOURCE(i2c_ctrl),
+ .offset = 0x00150000, .size = 0x10000, .irq_vec = 7,
+ .extra = &(struct ptp_ocp_i2c_info) {
+ .name = "xiic-i2c",
+ .fixed_rate = 50000000,
+ .data_size = sizeof(struct xiic_i2c_platform_data),
+ .data = &(struct xiic_i2c_platform_data) {
+ .num_devices = 2,
+ .devices = (struct i2c_board_info[]) {
+ { I2C_BOARD_INFO("24c02", 0x50) },
+ { I2C_BOARD_INFO("24mac402", 0x58),
+ .platform_data = "mac" },
+ },
+ },
+ },
+ },
+ {
+ OCP_SERIAL_RESOURCE(gnss_port),
+ .offset = 0x00160000 + 0x1000, .irq_vec = 3,
+ },
+ {
+ OCP_SERIAL_RESOURCE(gnss2_port),
+ .offset = 0x00170000 + 0x1000, .irq_vec = 4,
+ },
+ {
+ OCP_SERIAL_RESOURCE(mac_port),
+ .offset = 0x00180000 + 0x1000, .irq_vec = 5,
+ },
+ {
+ OCP_SERIAL_RESOURCE(nmea_port),
+ .offset = 0x00190000 + 0x1000, .irq_vec = 10,
+ },
+ {
+ OCP_SPI_RESOURCE(spi_flash),
+ .offset = 0x00310000, .size = 0x10000, .irq_vec = 9,
+ .extra = &(struct ptp_ocp_flash_info) {
+ .name = "xilinx_spi", .pci_offset = 0,
+ .data_size = sizeof(struct xspi_platform_data),
+ .data = &(struct xspi_platform_data) {
+ .num_chipselect = 1,
+ .bits_per_word = 8,
+ .num_devices = 1,
+ .devices = &(struct spi_board_info) {
+ .modalias = "spi-nor",
+ },
+ },
+ },
+ },
+ {
+ OCP_MEM_RESOURCE(freq_in[0]),
+ .offset = 0x01200000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(freq_in[1]),
+ .offset = 0x01210000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(freq_in[2]),
+ .offset = 0x01220000, .size = 0x10000,
+ },
+ {
+ OCP_MEM_RESOURCE(freq_in[3]),
+ .offset = 0x01230000, .size = 0x10000,
+ },
+ {
+ .setup = ptp_ocp_fb_board_init,
+ },
+ { }
+};
+
+static const struct pci_device_id ptp_ocp_pcidev_id[] = {
+ { PCI_DEVICE_DATA(FACEBOOK, TIMECARD, &ocp_fb_resource) },
+ { PCI_DEVICE_DATA(CELESTICA, TIMECARD, &ocp_fb_resource) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, ptp_ocp_pcidev_id);
+
+static DEFINE_MUTEX(ptp_ocp_lock);
+static DEFINE_IDR(ptp_ocp_idr);
+
+struct ocp_selector {
+ const char *name;
+ int value;
+};
+
+static const struct ocp_selector ptp_ocp_clock[] = {
+ { .name = "NONE", .value = 0 },
+ { .name = "TOD", .value = 1 },
+ { .name = "IRIG", .value = 2 },
+ { .name = "PPS", .value = 3 },
+ { .name = "PTP", .value = 4 },
+ { .name = "RTC", .value = 5 },
+ { .name = "DCF", .value = 6 },
+ { .name = "REGS", .value = 0xfe },
+ { .name = "EXT", .value = 0xff },
+ { }
+};
+
+#define SMA_DISABLE BIT(16)
+#define SMA_ENABLE BIT(15)
+#define SMA_SELECT_MASK GENMASK(14, 0)
+
+static const struct ocp_selector ptp_ocp_sma_in[] = {
+ { .name = "10Mhz", .value = 0x0000 },
+ { .name = "PPS1", .value = 0x0001 },
+ { .name = "PPS2", .value = 0x0002 },
+ { .name = "TS1", .value = 0x0004 },
+ { .name = "TS2", .value = 0x0008 },
+ { .name = "IRIG", .value = 0x0010 },
+ { .name = "DCF", .value = 0x0020 },
+ { .name = "TS3", .value = 0x0040 },
+ { .name = "TS4", .value = 0x0080 },
+ { .name = "FREQ1", .value = 0x0100 },
+ { .name = "FREQ2", .value = 0x0200 },
+ { .name = "FREQ3", .value = 0x0400 },
+ { .name = "FREQ4", .value = 0x0800 },
+ { .name = "None", .value = SMA_DISABLE },
+ { }
+};
+
+static const struct ocp_selector ptp_ocp_sma_out[] = {
+ { .name = "10Mhz", .value = 0x0000 },
+ { .name = "PHC", .value = 0x0001 },
+ { .name = "MAC", .value = 0x0002 },
+ { .name = "GNSS1", .value = 0x0004 },
+ { .name = "GNSS2", .value = 0x0008 },
+ { .name = "IRIG", .value = 0x0010 },
+ { .name = "DCF", .value = 0x0020 },
+ { .name = "GEN1", .value = 0x0040 },
+ { .name = "GEN2", .value = 0x0080 },
+ { .name = "GEN3", .value = 0x0100 },
+ { .name = "GEN4", .value = 0x0200 },
+ { .name = "GND", .value = 0x2000 },
+ { .name = "VCC", .value = 0x4000 },
+ { }
+};
+
+struct ocp_sma_op {
+ const struct ocp_selector *tbl[2];
+ void (*init)(struct ptp_ocp *bp);
+ u32 (*get)(struct ptp_ocp *bp, int sma_nr);
+ int (*set_inputs)(struct ptp_ocp *bp, int sma_nr, u32 val);
+ int (*set_output)(struct ptp_ocp *bp, int sma_nr, u32 val);
+};
+
+static void
+ptp_ocp_sma_init(struct ptp_ocp *bp)
+{
+ return bp->sma_op->init(bp);
+}
+
+static u32
+ptp_ocp_sma_get(struct ptp_ocp *bp, int sma_nr)
+{
+ return bp->sma_op->get(bp, sma_nr);
+}
+
+static int
+ptp_ocp_sma_set_inputs(struct ptp_ocp *bp, int sma_nr, u32 val)
+{
+ return bp->sma_op->set_inputs(bp, sma_nr, val);
+}
+
+static int
+ptp_ocp_sma_set_output(struct ptp_ocp *bp, int sma_nr, u32 val)
+{
+ return bp->sma_op->set_output(bp, sma_nr, val);
+}
+
+static const char *
+ptp_ocp_select_name_from_val(const struct ocp_selector *tbl, int val)
+{
+ int i;
+
+ for (i = 0; tbl[i].name; i++)
+ if (tbl[i].value == val)
+ return tbl[i].name;
+ return NULL;
+}
+
+static int
+ptp_ocp_select_val_from_name(const struct ocp_selector *tbl, const char *name)
+{
+ const char *select;
+ int i;
+
+ for (i = 0; tbl[i].name; i++) {
+ select = tbl[i].name;
+ if (!strncasecmp(name, select, strlen(select)))
+ return tbl[i].value;
+ }
+ return -EINVAL;
+}
+
+static ssize_t
+ptp_ocp_select_table_show(const struct ocp_selector *tbl, char *buf)
+{
+ ssize_t count;
+ int i;
+
+ count = 0;
+ for (i = 0; tbl[i].name; i++)
+ count += sysfs_emit_at(buf, count, "%s ", tbl[i].name);
+ if (count)
+ count--;
+ count += sysfs_emit_at(buf, count, "\n");
+ return count;
+}
+
+static int
+__ptp_ocp_gettime_locked(struct ptp_ocp *bp, struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ u32 ctrl, time_sec, time_ns;
+ int i;
+
+ ptp_read_system_prets(sts);
+
+ ctrl = OCP_CTRL_READ_TIME_REQ | OCP_CTRL_ENABLE;
+ iowrite32(ctrl, &bp->reg->ctrl);
+
+ for (i = 0; i < 100; i++) {
+ ctrl = ioread32(&bp->reg->ctrl);
+ if (ctrl & OCP_CTRL_READ_TIME_DONE)
+ break;
+ }
+ ptp_read_system_postts(sts);
+
+ if (sts && bp->ts_window_adjust) {
+ s64 ns = timespec64_to_ns(&sts->post_ts);
+
+ sts->post_ts = ns_to_timespec64(ns - bp->ts_window_adjust);
+ }
+
+ time_ns = ioread32(&bp->reg->time_ns);
+ time_sec = ioread32(&bp->reg->time_sec);
+
+ ts->tv_sec = time_sec;
+ ts->tv_nsec = time_ns;
+
+ return ctrl & OCP_CTRL_READ_TIME_DONE ? 0 : -ETIMEDOUT;
+}
+
+static int
+ptp_ocp_gettimex(struct ptp_clock_info *ptp_info, struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info);
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&bp->lock, flags);
+ err = __ptp_ocp_gettime_locked(bp, ts, sts);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return err;
+}
+
+static void
+__ptp_ocp_settime_locked(struct ptp_ocp *bp, const struct timespec64 *ts)
+{
+ u32 ctrl, time_sec, time_ns;
+ u32 select;
+
+ time_ns = ts->tv_nsec;
+ time_sec = ts->tv_sec;
+
+ select = ioread32(&bp->reg->select);
+ iowrite32(OCP_SELECT_CLK_REG, &bp->reg->select);
+
+ iowrite32(time_ns, &bp->reg->adjust_ns);
+ iowrite32(time_sec, &bp->reg->adjust_sec);
+
+ ctrl = OCP_CTRL_ADJUST_TIME | OCP_CTRL_ENABLE;
+ iowrite32(ctrl, &bp->reg->ctrl);
+
+ /* restore clock selection */
+ iowrite32(select >> 16, &bp->reg->select);
+}
+
+static int
+ptp_ocp_settime(struct ptp_clock_info *ptp_info, const struct timespec64 *ts)
+{
+ struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info);
+ unsigned long flags;
+
+ spin_lock_irqsave(&bp->lock, flags);
+ __ptp_ocp_settime_locked(bp, ts);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return 0;
+}
+
+static void
+__ptp_ocp_adjtime_locked(struct ptp_ocp *bp, u32 adj_val)
+{
+ u32 select, ctrl;
+
+ select = ioread32(&bp->reg->select);
+ iowrite32(OCP_SELECT_CLK_REG, &bp->reg->select);
+
+ iowrite32(adj_val, &bp->reg->offset_ns);
+ iowrite32(NSEC_PER_SEC, &bp->reg->offset_window_ns);
+
+ ctrl = OCP_CTRL_ADJUST_OFFSET | OCP_CTRL_ENABLE;
+ iowrite32(ctrl, &bp->reg->ctrl);
+
+ /* restore clock selection */
+ iowrite32(select >> 16, &bp->reg->select);
+}
+
+static void
+ptp_ocp_adjtime_coarse(struct ptp_ocp *bp, s64 delta_ns)
+{
+ struct timespec64 ts;
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&bp->lock, flags);
+ err = __ptp_ocp_gettime_locked(bp, &ts, NULL);
+ if (likely(!err)) {
+ set_normalized_timespec64(&ts, ts.tv_sec,
+ ts.tv_nsec + delta_ns);
+ __ptp_ocp_settime_locked(bp, &ts);
+ }
+ spin_unlock_irqrestore(&bp->lock, flags);
+}
+
+static int
+ptp_ocp_adjtime(struct ptp_clock_info *ptp_info, s64 delta_ns)
+{
+ struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info);
+ unsigned long flags;
+ u32 adj_ns, sign;
+
+ if (delta_ns > NSEC_PER_SEC || -delta_ns > NSEC_PER_SEC) {
+ ptp_ocp_adjtime_coarse(bp, delta_ns);
+ return 0;
+ }
+
+ sign = delta_ns < 0 ? BIT(31) : 0;
+ adj_ns = sign ? -delta_ns : delta_ns;
+
+ spin_lock_irqsave(&bp->lock, flags);
+ __ptp_ocp_adjtime_locked(bp, sign | adj_ns);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return 0;
+}
+
+static int
+ptp_ocp_null_adjfine(struct ptp_clock_info *ptp_info, long scaled_ppm)
+{
+ if (scaled_ppm == 0)
+ return 0;
+
+ return -EOPNOTSUPP;
+}
+
+static int
+ptp_ocp_null_adjphase(struct ptp_clock_info *ptp_info, s32 phase_ns)
+{
+ return -EOPNOTSUPP;
+}
+
+static int
+ptp_ocp_enable(struct ptp_clock_info *ptp_info, struct ptp_clock_request *rq,
+ int on)
+{
+ struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info);
+ struct ptp_ocp_ext_src *ext = NULL;
+ u32 req;
+ int err;
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_EXTTS:
+ req = OCP_REQ_TIMESTAMP;
+ switch (rq->extts.index) {
+ case 0:
+ ext = bp->ts0;
+ break;
+ case 1:
+ ext = bp->ts1;
+ break;
+ case 2:
+ ext = bp->ts2;
+ break;
+ case 3:
+ ext = bp->ts3;
+ break;
+ case 4:
+ ext = bp->ts4;
+ break;
+ case 5:
+ ext = bp->pps;
+ break;
+ }
+ break;
+ case PTP_CLK_REQ_PPS:
+ req = OCP_REQ_PPS;
+ ext = bp->pps;
+ break;
+ case PTP_CLK_REQ_PEROUT:
+ switch (rq->perout.index) {
+ case 0:
+ /* This is a request for 1PPS on an output SMA.
+ * Allow, but assume manual configuration.
+ */
+ if (on && (rq->perout.period.sec != 1 ||
+ rq->perout.period.nsec != 0))
+ return -EINVAL;
+ return 0;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ req = rq->perout.index - 1;
+ ext = bp->signal_out[req];
+ err = ptp_ocp_signal_from_perout(bp, req, &rq->perout);
+ if (err)
+ return err;
+ break;
+ }
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ err = -ENXIO;
+ if (ext)
+ err = ext->info->enable(ext, req, on);
+
+ return err;
+}
+
+static int
+ptp_ocp_verify(struct ptp_clock_info *ptp_info, unsigned pin,
+ enum ptp_pin_function func, unsigned chan)
+{
+ struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info);
+ char buf[16];
+
+ switch (func) {
+ case PTP_PF_NONE:
+ snprintf(buf, sizeof(buf), "IN: None");
+ break;
+ case PTP_PF_EXTTS:
+ /* Allow timestamps, but require sysfs configuration. */
+ return 0;
+ case PTP_PF_PEROUT:
+ /* channel 0 is 1PPS from PHC.
+ * channels 1..4 are the frequency generators.
+ */
+ if (chan)
+ snprintf(buf, sizeof(buf), "OUT: GEN%d", chan);
+ else
+ snprintf(buf, sizeof(buf), "OUT: PHC");
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return ptp_ocp_sma_store(bp, buf, pin + 1);
+}
+
+static const struct ptp_clock_info ptp_ocp_clock_info = {
+ .owner = THIS_MODULE,
+ .name = KBUILD_MODNAME,
+ .max_adj = 100000000,
+ .gettimex64 = ptp_ocp_gettimex,
+ .settime64 = ptp_ocp_settime,
+ .adjtime = ptp_ocp_adjtime,
+ .adjfine = ptp_ocp_null_adjfine,
+ .adjphase = ptp_ocp_null_adjphase,
+ .enable = ptp_ocp_enable,
+ .verify = ptp_ocp_verify,
+ .pps = true,
+ .n_ext_ts = 6,
+ .n_per_out = 5,
+};
+
+static void
+__ptp_ocp_clear_drift_locked(struct ptp_ocp *bp)
+{
+ u32 ctrl, select;
+
+ select = ioread32(&bp->reg->select);
+ iowrite32(OCP_SELECT_CLK_REG, &bp->reg->select);
+
+ iowrite32(0, &bp->reg->drift_ns);
+
+ ctrl = OCP_CTRL_ADJUST_DRIFT | OCP_CTRL_ENABLE;
+ iowrite32(ctrl, &bp->reg->ctrl);
+
+ /* restore clock selection */
+ iowrite32(select >> 16, &bp->reg->select);
+}
+
+static void
+ptp_ocp_utc_distribute(struct ptp_ocp *bp, u32 val)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&bp->lock, flags);
+
+ bp->utc_tai_offset = val;
+
+ if (bp->irig_out)
+ iowrite32(val, &bp->irig_out->adj_sec);
+ if (bp->dcf_out)
+ iowrite32(val, &bp->dcf_out->adj_sec);
+ if (bp->nmea_out)
+ iowrite32(val, &bp->nmea_out->adj_sec);
+
+ spin_unlock_irqrestore(&bp->lock, flags);
+}
+
+static void
+ptp_ocp_watchdog(struct timer_list *t)
+{
+ struct ptp_ocp *bp = from_timer(bp, t, watchdog);
+ unsigned long flags;
+ u32 status, utc_offset;
+
+ status = ioread32(&bp->pps_to_clk->status);
+
+ if (status & PPS_STATUS_SUPERV_ERR) {
+ iowrite32(status, &bp->pps_to_clk->status);
+ if (!bp->gnss_lost) {
+ spin_lock_irqsave(&bp->lock, flags);
+ __ptp_ocp_clear_drift_locked(bp);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ bp->gnss_lost = ktime_get_real_seconds();
+ }
+
+ } else if (bp->gnss_lost) {
+ bp->gnss_lost = 0;
+ }
+
+ /* if GNSS provides correct data we can rely on
+ * it to get leap second information
+ */
+ if (bp->tod) {
+ status = ioread32(&bp->tod->utc_status);
+ utc_offset = status & TOD_STATUS_UTC_MASK;
+ if (status & TOD_STATUS_UTC_VALID &&
+ utc_offset != bp->utc_tai_offset)
+ ptp_ocp_utc_distribute(bp, utc_offset);
+ }
+
+ mod_timer(&bp->watchdog, jiffies + HZ);
+}
+
+static void
+ptp_ocp_estimate_pci_timing(struct ptp_ocp *bp)
+{
+ ktime_t start, end;
+ ktime_t delay;
+ u32 ctrl;
+
+ ctrl = ioread32(&bp->reg->ctrl);
+ ctrl = OCP_CTRL_READ_TIME_REQ | OCP_CTRL_ENABLE;
+
+ iowrite32(ctrl, &bp->reg->ctrl);
+
+ start = ktime_get_ns();
+
+ ctrl = ioread32(&bp->reg->ctrl);
+
+ end = ktime_get_ns();
+
+ delay = end - start;
+ bp->ts_window_adjust = (delay >> 5) * 3;
+}
+
+static int
+ptp_ocp_init_clock(struct ptp_ocp *bp)
+{
+ struct timespec64 ts;
+ bool sync;
+ u32 ctrl;
+
+ ctrl = OCP_CTRL_ENABLE;
+ iowrite32(ctrl, &bp->reg->ctrl);
+
+ /* NO DRIFT Correction */
+ /* offset_p:i 1/8, offset_i: 1/16, drift_p: 0, drift_i: 0 */
+ iowrite32(0x2000, &bp->reg->servo_offset_p);
+ iowrite32(0x1000, &bp->reg->servo_offset_i);
+ iowrite32(0, &bp->reg->servo_drift_p);
+ iowrite32(0, &bp->reg->servo_drift_i);
+
+ /* latch servo values */
+ ctrl |= OCP_CTRL_ADJUST_SERVO;
+ iowrite32(ctrl, &bp->reg->ctrl);
+
+ if ((ioread32(&bp->reg->ctrl) & OCP_CTRL_ENABLE) == 0) {
+ dev_err(&bp->pdev->dev, "clock not enabled\n");
+ return -ENODEV;
+ }
+
+ ptp_ocp_estimate_pci_timing(bp);
+
+ sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
+ if (!sync) {
+ ktime_get_clocktai_ts64(&ts);
+ ptp_ocp_settime(&bp->ptp_info, &ts);
+ }
+
+ /* If there is a clock supervisor, then enable the watchdog */
+ if (bp->pps_to_clk) {
+ timer_setup(&bp->watchdog, ptp_ocp_watchdog, 0);
+ mod_timer(&bp->watchdog, jiffies + HZ);
+ }
+
+ return 0;
+}
+
+static void
+ptp_ocp_tod_init(struct ptp_ocp *bp)
+{
+ u32 ctrl, reg;
+
+ ctrl = ioread32(&bp->tod->ctrl);
+ ctrl |= TOD_CTRL_PROTOCOL | TOD_CTRL_ENABLE;
+ ctrl &= ~(TOD_CTRL_DISABLE_FMT_A | TOD_CTRL_DISABLE_FMT_B);
+ iowrite32(ctrl, &bp->tod->ctrl);
+
+ reg = ioread32(&bp->tod->utc_status);
+ if (reg & TOD_STATUS_UTC_VALID)
+ ptp_ocp_utc_distribute(bp, reg & TOD_STATUS_UTC_MASK);
+}
+
+static const char *
+ptp_ocp_tod_proto_name(const int idx)
+{
+ static const char * const proto_name[] = {
+ "NMEA", "NMEA_ZDA", "NMEA_RMC", "NMEA_none",
+ "UBX", "UBX_UTC", "UBX_LS", "UBX_none"
+ };
+ return proto_name[idx];
+}
+
+static const char *
+ptp_ocp_tod_gnss_name(int idx)
+{
+ static const char * const gnss_name[] = {
+ "ALL", "COMBINED", "GPS", "GLONASS", "GALILEO", "BEIDOU",
+ "Unknown"
+ };
+ if (idx >= ARRAY_SIZE(gnss_name))
+ idx = ARRAY_SIZE(gnss_name) - 1;
+ return gnss_name[idx];
+}
+
+struct ptp_ocp_nvmem_match_info {
+ struct ptp_ocp *bp;
+ const void * const tag;
+};
+
+static int
+ptp_ocp_nvmem_match(struct device *dev, const void *data)
+{
+ const struct ptp_ocp_nvmem_match_info *info = data;
+
+ dev = dev->parent;
+ if (!i2c_verify_client(dev) || info->tag != dev->platform_data)
+ return 0;
+
+ while ((dev = dev->parent))
+ if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME))
+ return info->bp == dev_get_drvdata(dev);
+ return 0;
+}
+
+static inline struct nvmem_device *
+ptp_ocp_nvmem_device_get(struct ptp_ocp *bp, const void * const tag)
+{
+ struct ptp_ocp_nvmem_match_info info = { .bp = bp, .tag = tag };
+
+ return nvmem_device_find(&info, ptp_ocp_nvmem_match);
+}
+
+static inline void
+ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp)
+{
+ if (!IS_ERR_OR_NULL(*nvmemp))
+ nvmem_device_put(*nvmemp);
+ *nvmemp = NULL;
+}
+
+static void
+ptp_ocp_read_eeprom(struct ptp_ocp *bp)
+{
+ const struct ptp_ocp_eeprom_map *map;
+ struct nvmem_device *nvmem;
+ const void *tag;
+ int ret;
+
+ if (!bp->i2c_ctrl)
+ return;
+
+ tag = NULL;
+ nvmem = NULL;
+
+ for (map = bp->eeprom_map; map->len; map++) {
+ if (map->tag != tag) {
+ tag = map->tag;
+ ptp_ocp_nvmem_device_put(&nvmem);
+ }
+ if (!nvmem) {
+ nvmem = ptp_ocp_nvmem_device_get(bp, tag);
+ if (IS_ERR(nvmem)) {
+ ret = PTR_ERR(nvmem);
+ goto fail;
+ }
+ }
+ ret = nvmem_device_read(nvmem, map->off, map->len,
+ BP_MAP_ENTRY_ADDR(bp, map));
+ if (ret != map->len)
+ goto fail;
+ }
+
+ bp->has_eeprom_data = true;
+
+out:
+ ptp_ocp_nvmem_device_put(&nvmem);
+ return;
+
+fail:
+ dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret);
+ goto out;
+}
+
+static struct device *
+ptp_ocp_find_flash(struct ptp_ocp *bp)
+{
+ struct device *dev, *last;
+
+ last = NULL;
+ dev = &bp->spi_flash->dev;
+
+ while ((dev = device_find_any_child(dev))) {
+ if (!strcmp("mtd", dev_bus_name(dev)))
+ break;
+ put_device(last);
+ last = dev;
+ }
+ put_device(last);
+
+ return dev;
+}
+
+static int
+ptp_ocp_devlink_fw_image(struct devlink *devlink, const struct firmware *fw,
+ const u8 **data, size_t *size)
+{
+ struct ptp_ocp *bp = devlink_priv(devlink);
+ const struct ptp_ocp_firmware_header *hdr;
+ size_t offset, length;
+ u16 crc;
+
+ hdr = (const struct ptp_ocp_firmware_header *)fw->data;
+ if (memcmp(hdr->magic, OCP_FIRMWARE_MAGIC_HEADER, 4)) {
+ devlink_flash_update_status_notify(devlink,
+ "No firmware header found, flashing raw image",
+ NULL, 0, 0);
+ offset = 0;
+ length = fw->size;
+ goto out;
+ }
+
+ if (be16_to_cpu(hdr->pci_vendor_id) != bp->pdev->vendor ||
+ be16_to_cpu(hdr->pci_device_id) != bp->pdev->device) {
+ devlink_flash_update_status_notify(devlink,
+ "Firmware image compatibility check failed",
+ NULL, 0, 0);
+ return -EINVAL;
+ }
+
+ offset = sizeof(*hdr);
+ length = be32_to_cpu(hdr->image_size);
+ if (length != (fw->size - offset)) {
+ devlink_flash_update_status_notify(devlink,
+ "Firmware image size check failed",
+ NULL, 0, 0);
+ return -EINVAL;
+ }
+
+ crc = crc16(0xffff, &fw->data[offset], length);
+ if (be16_to_cpu(hdr->crc) != crc) {
+ devlink_flash_update_status_notify(devlink,
+ "Firmware image CRC check failed",
+ NULL, 0, 0);
+ return -EINVAL;
+ }
+
+out:
+ *data = &fw->data[offset];
+ *size = length;
+
+ return 0;
+}
+
+static int
+ptp_ocp_devlink_flash(struct devlink *devlink, struct device *dev,
+ const struct firmware *fw)
+{
+ struct mtd_info *mtd = dev_get_drvdata(dev);
+ struct ptp_ocp *bp = devlink_priv(devlink);
+ size_t off, len, size, resid, wrote;
+ struct erase_info erase;
+ size_t base, blksz;
+ const u8 *data;
+ int err;
+
+ err = ptp_ocp_devlink_fw_image(devlink, fw, &data, &size);
+ if (err)
+ goto out;
+
+ off = 0;
+ base = bp->flash_start;
+ blksz = 4096;
+ resid = size;
+
+ while (resid) {
+ devlink_flash_update_status_notify(devlink, "Flashing",
+ NULL, off, size);
+
+ len = min_t(size_t, resid, blksz);
+ erase.addr = base + off;
+ erase.len = blksz;
+
+ err = mtd_erase(mtd, &erase);
+ if (err)
+ goto out;
+
+ err = mtd_write(mtd, base + off, len, &wrote, data + off);
+ if (err)
+ goto out;
+
+ off += blksz;
+ resid -= len;
+ }
+out:
+ return err;
+}
+
+static int
+ptp_ocp_devlink_flash_update(struct devlink *devlink,
+ struct devlink_flash_update_params *params,
+ struct netlink_ext_ack *extack)
+{
+ struct ptp_ocp *bp = devlink_priv(devlink);
+ struct device *dev;
+ const char *msg;
+ int err;
+
+ dev = ptp_ocp_find_flash(bp);
+ if (!dev) {
+ dev_err(&bp->pdev->dev, "Can't find Flash SPI adapter\n");
+ return -ENODEV;
+ }
+
+ devlink_flash_update_status_notify(devlink, "Preparing to flash",
+ NULL, 0, 0);
+
+ err = ptp_ocp_devlink_flash(devlink, dev, params->fw);
+
+ msg = err ? "Flash error" : "Flash complete";
+ devlink_flash_update_status_notify(devlink, msg, NULL, 0, 0);
+
+ put_device(dev);
+ return err;
+}
+
+static int
+ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,
+ struct netlink_ext_ack *extack)
+{
+ struct ptp_ocp *bp = devlink_priv(devlink);
+ const char *fw_image;
+ char buf[32];
+ int err;
+
+ err = devlink_info_driver_name_put(req, KBUILD_MODNAME);
+ if (err)
+ return err;
+
+ fw_image = bp->fw_loader ? "loader" : "fw";
+ sprintf(buf, "%d.%d", bp->fw_tag, bp->fw_version);
+ err = devlink_info_version_running_put(req, fw_image, buf);
+ if (err)
+ return err;
+
+ if (!bp->has_eeprom_data) {
+ ptp_ocp_read_eeprom(bp);
+ if (!bp->has_eeprom_data)
+ return 0;
+ }
+
+ sprintf(buf, "%pM", bp->serial);
+ err = devlink_info_serial_number_put(req, buf);
+ if (err)
+ return err;
+
+ err = devlink_info_version_fixed_put(req,
+ DEVLINK_INFO_VERSION_GENERIC_BOARD_ID,
+ bp->board_id);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static const struct devlink_ops ptp_ocp_devlink_ops = {
+ .flash_update = ptp_ocp_devlink_flash_update,
+ .info_get = ptp_ocp_devlink_info_get,
+};
+
+static void __iomem *
+__ptp_ocp_get_mem(struct ptp_ocp *bp, resource_size_t start, int size)
+{
+ struct resource res = DEFINE_RES_MEM_NAMED(start, size, "ptp_ocp");
+
+ return devm_ioremap_resource(&bp->pdev->dev, &res);
+}
+
+static void __iomem *
+ptp_ocp_get_mem(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ resource_size_t start;
+
+ start = pci_resource_start(bp->pdev, 0) + r->offset;
+ return __ptp_ocp_get_mem(bp, start, r->size);
+}
+
+static void
+ptp_ocp_set_irq_resource(struct resource *res, int irq)
+{
+ struct resource r = DEFINE_RES_IRQ(irq);
+ *res = r;
+}
+
+static void
+ptp_ocp_set_mem_resource(struct resource *res, resource_size_t start, int size)
+{
+ struct resource r = DEFINE_RES_MEM(start, size);
+ *res = r;
+}
+
+static int
+ptp_ocp_register_spi(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ struct ptp_ocp_flash_info *info;
+ struct pci_dev *pdev = bp->pdev;
+ struct platform_device *p;
+ struct resource res[2];
+ resource_size_t start;
+ int id;
+
+ start = pci_resource_start(pdev, 0) + r->offset;
+ ptp_ocp_set_mem_resource(&res[0], start, r->size);
+ ptp_ocp_set_irq_resource(&res[1], pci_irq_vector(pdev, r->irq_vec));
+
+ info = r->extra;
+ id = pci_dev_id(pdev) << 1;
+ id += info->pci_offset;
+
+ p = platform_device_register_resndata(&pdev->dev, info->name, id,
+ res, 2, info->data,
+ info->data_size);
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ bp_assign_entry(bp, r, p);
+
+ return 0;
+}
+
+static struct platform_device *
+ptp_ocp_i2c_bus(struct pci_dev *pdev, struct ocp_resource *r, int id)
+{
+ struct ptp_ocp_i2c_info *info;
+ struct resource res[2];
+ resource_size_t start;
+
+ info = r->extra;
+ start = pci_resource_start(pdev, 0) + r->offset;
+ ptp_ocp_set_mem_resource(&res[0], start, r->size);
+ ptp_ocp_set_irq_resource(&res[1], pci_irq_vector(pdev, r->irq_vec));
+
+ return platform_device_register_resndata(&pdev->dev, info->name,
+ id, res, 2,
+ info->data, info->data_size);
+}
+
+static int
+ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ struct pci_dev *pdev = bp->pdev;
+ struct ptp_ocp_i2c_info *info;
+ struct platform_device *p;
+ struct clk_hw *clk;
+ char buf[32];
+ int id;
+
+ info = r->extra;
+ id = pci_dev_id(bp->pdev);
+
+ sprintf(buf, "AXI.%d", id);
+ clk = clk_hw_register_fixed_rate(&pdev->dev, buf, NULL, 0,
+ info->fixed_rate);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ bp->i2c_clk = clk;
+
+ sprintf(buf, "%s.%d", info->name, id);
+ devm_clk_hw_register_clkdev(&pdev->dev, clk, NULL, buf);
+ p = ptp_ocp_i2c_bus(bp->pdev, r, id);
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
+ bp_assign_entry(bp, r, p);
+
+ return 0;
+}
+
+/* The expectation is that this is triggered only on error. */
+static irqreturn_t
+ptp_ocp_signal_irq(int irq, void *priv)
+{
+ struct ptp_ocp_ext_src *ext = priv;
+ struct signal_reg __iomem *reg = ext->mem;
+ struct ptp_ocp *bp = ext->bp;
+ u32 enable, status;
+ int gen;
+
+ gen = ext->info->index - 1;
+
+ enable = ioread32(&reg->enable);
+ status = ioread32(&reg->status);
+
+ /* disable generator on error */
+ if (status || !enable) {
+ iowrite32(0, &reg->intr_mask);
+ iowrite32(0, &reg->enable);
+ bp->signal[gen].running = false;
+ }
+
+ iowrite32(0, &reg->intr); /* ack interrupt */
+
+ return IRQ_HANDLED;
+}
+
+static int
+ptp_ocp_signal_set(struct ptp_ocp *bp, int gen, struct ptp_ocp_signal *s)
+{
+ struct ptp_system_timestamp sts;
+ struct timespec64 ts;
+ ktime_t start_ns;
+ int err;
+
+ if (!s->period)
+ return 0;
+
+ if (!s->pulse)
+ s->pulse = ktime_divns(s->period * s->duty, 100);
+
+ err = ptp_ocp_gettimex(&bp->ptp_info, &ts, &sts);
+ if (err)
+ return err;
+
+ start_ns = ktime_set(ts.tv_sec, ts.tv_nsec) + NSEC_PER_MSEC;
+ if (!s->start) {
+ /* roundup() does not work on 32-bit systems */
+ s->start = DIV64_U64_ROUND_UP(start_ns, s->period);
+ s->start = ktime_add(s->start, s->phase);
+ }
+
+ if (s->duty < 1 || s->duty > 99)
+ return -EINVAL;
+
+ if (s->pulse < 1 || s->pulse > s->period)
+ return -EINVAL;
+
+ if (s->start < start_ns)
+ return -EINVAL;
+
+ bp->signal[gen] = *s;
+
+ return 0;
+}
+
+static int
+ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen,
+ struct ptp_perout_request *req)
+{
+ struct ptp_ocp_signal s = { };
+
+ s.polarity = bp->signal[gen].polarity;
+ s.period = ktime_set(req->period.sec, req->period.nsec);
+ if (!s.period)
+ return 0;
+
+ if (req->flags & PTP_PEROUT_DUTY_CYCLE) {
+ s.pulse = ktime_set(req->on.sec, req->on.nsec);
+ s.duty = ktime_divns(s.pulse * 100, s.period);
+ }
+
+ if (req->flags & PTP_PEROUT_PHASE)
+ s.phase = ktime_set(req->phase.sec, req->phase.nsec);
+ else
+ s.start = ktime_set(req->start.sec, req->start.nsec);
+
+ return ptp_ocp_signal_set(bp, gen, &s);
+}
+
+static int
+ptp_ocp_signal_enable(void *priv, u32 req, bool enable)
+{
+ struct ptp_ocp_ext_src *ext = priv;
+ struct signal_reg __iomem *reg = ext->mem;
+ struct ptp_ocp *bp = ext->bp;
+ struct timespec64 ts;
+ int gen;
+
+ gen = ext->info->index - 1;
+
+ iowrite32(0, &reg->intr_mask);
+ iowrite32(0, &reg->enable);
+ bp->signal[gen].running = false;
+ if (!enable)
+ return 0;
+
+ ts = ktime_to_timespec64(bp->signal[gen].start);
+ iowrite32(ts.tv_sec, &reg->start_sec);
+ iowrite32(ts.tv_nsec, &reg->start_ns);
+
+ ts = ktime_to_timespec64(bp->signal[gen].period);
+ iowrite32(ts.tv_sec, &reg->period_sec);
+ iowrite32(ts.tv_nsec, &reg->period_ns);
+
+ ts = ktime_to_timespec64(bp->signal[gen].pulse);
+ iowrite32(ts.tv_sec, &reg->pulse_sec);
+ iowrite32(ts.tv_nsec, &reg->pulse_ns);
+
+ iowrite32(bp->signal[gen].polarity, &reg->polarity);
+ iowrite32(0, &reg->repeat_count);
+
+ iowrite32(0, &reg->intr); /* clear interrupt state */
+ iowrite32(1, &reg->intr_mask); /* enable interrupt */
+ iowrite32(3, &reg->enable); /* valid & enable */
+
+ bp->signal[gen].running = true;
+
+ return 0;
+}
+
+static irqreturn_t
+ptp_ocp_ts_irq(int irq, void *priv)
+{
+ struct ptp_ocp_ext_src *ext = priv;
+ struct ts_reg __iomem *reg = ext->mem;
+ struct ptp_clock_event ev;
+ u32 sec, nsec;
+
+ if (ext == ext->bp->pps) {
+ if (ext->bp->pps_req_map & OCP_REQ_PPS) {
+ ev.type = PTP_CLOCK_PPS;
+ ptp_clock_event(ext->bp->ptp, &ev);
+ }
+
+ if ((ext->bp->pps_req_map & ~OCP_REQ_PPS) == 0)
+ goto out;
+ }
+
+ /* XXX should fix API - this converts s/ns -> ts -> s/ns */
+ sec = ioread32(&reg->time_sec);
+ nsec = ioread32(&reg->time_ns);
+
+ ev.type = PTP_CLOCK_EXTTS;
+ ev.index = ext->info->index;
+ ev.timestamp = sec * NSEC_PER_SEC + nsec;
+
+ ptp_clock_event(ext->bp->ptp, &ev);
+
+out:
+ iowrite32(1, &reg->intr); /* write 1 to ack */
+
+ return IRQ_HANDLED;
+}
+
+static int
+ptp_ocp_ts_enable(void *priv, u32 req, bool enable)
+{
+ struct ptp_ocp_ext_src *ext = priv;
+ struct ts_reg __iomem *reg = ext->mem;
+ struct ptp_ocp *bp = ext->bp;
+
+ if (ext == bp->pps) {
+ u32 old_map = bp->pps_req_map;
+
+ if (enable)
+ bp->pps_req_map |= req;
+ else
+ bp->pps_req_map &= ~req;
+
+ /* if no state change, just return */
+ if ((!!old_map ^ !!bp->pps_req_map) == 0)
+ return 0;
+ }
+
+ if (enable) {
+ iowrite32(1, &reg->enable);
+ iowrite32(1, &reg->intr_mask);
+ iowrite32(1, &reg->intr);
+ } else {
+ iowrite32(0, &reg->intr_mask);
+ iowrite32(0, &reg->enable);
+ }
+
+ return 0;
+}
+
+static void
+ptp_ocp_unregister_ext(struct ptp_ocp_ext_src *ext)
+{
+ ext->info->enable(ext, ~0, false);
+ pci_free_irq(ext->bp->pdev, ext->irq_vec, ext);
+ kfree(ext);
+}
+
+static int
+ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ struct pci_dev *pdev = bp->pdev;
+ struct ptp_ocp_ext_src *ext;
+ int err;
+
+ ext = kzalloc(sizeof(*ext), GFP_KERNEL);
+ if (!ext)
+ return -ENOMEM;
+
+ ext->mem = ptp_ocp_get_mem(bp, r);
+ if (IS_ERR(ext->mem)) {
+ err = PTR_ERR(ext->mem);
+ goto out;
+ }
+
+ ext->bp = bp;
+ ext->info = r->extra;
+ ext->irq_vec = r->irq_vec;
+
+ err = pci_request_irq(pdev, r->irq_vec, ext->info->irq_fcn, NULL,
+ ext, "ocp%d.%s", bp->id, r->name);
+ if (err) {
+ dev_err(&pdev->dev, "Could not get irq %d\n", r->irq_vec);
+ goto out;
+ }
+
+ bp_assign_entry(bp, r, ext);
+
+ return 0;
+
+out:
+ kfree(ext);
+ return err;
+}
+
+static int
+ptp_ocp_serial_line(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ struct pci_dev *pdev = bp->pdev;
+ struct uart_8250_port uart;
+
+ /* Setting UPF_IOREMAP and leaving port.membase unspecified lets
+ * the serial port device claim and release the pci resource.
+ */
+ memset(&uart, 0, sizeof(uart));
+ uart.port.dev = &pdev->dev;
+ uart.port.iotype = UPIO_MEM;
+ uart.port.regshift = 2;
+ uart.port.mapbase = pci_resource_start(pdev, 0) + r->offset;
+ uart.port.irq = pci_irq_vector(pdev, r->irq_vec);
+ uart.port.uartclk = 50000000;
+ uart.port.flags = UPF_FIXED_TYPE | UPF_IOREMAP | UPF_NO_THRE_TEST;
+ uart.port.type = PORT_16550A;
+
+ return serial8250_register_8250_port(&uart);
+}
+
+static int
+ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ int port;
+
+ port = ptp_ocp_serial_line(bp, r);
+ if (port < 0)
+ return port;
+
+ bp_assign_entry(bp, r, port);
+
+ return 0;
+}
+
+static int
+ptp_ocp_register_mem(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ void __iomem *mem;
+
+ mem = ptp_ocp_get_mem(bp, r);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ bp_assign_entry(bp, r, mem);
+
+ return 0;
+}
+
+static void
+ptp_ocp_nmea_out_init(struct ptp_ocp *bp)
+{
+ if (!bp->nmea_out)
+ return;
+
+ iowrite32(0, &bp->nmea_out->ctrl); /* disable */
+ iowrite32(7, &bp->nmea_out->uart_baud); /* 115200 */
+ iowrite32(1, &bp->nmea_out->ctrl); /* enable */
+}
+
+static void
+_ptp_ocp_signal_init(struct ptp_ocp_signal *s, struct signal_reg __iomem *reg)
+{
+ u32 val;
+
+ iowrite32(0, &reg->enable); /* disable */
+
+ val = ioread32(&reg->polarity);
+ s->polarity = val ? true : false;
+ s->duty = 50;
+}
+
+static void
+ptp_ocp_signal_init(struct ptp_ocp *bp)
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ if (bp->signal_out[i])
+ _ptp_ocp_signal_init(&bp->signal[i],
+ bp->signal_out[i]->mem);
+}
+
+static void
+ptp_ocp_attr_group_del(struct ptp_ocp *bp)
+{
+ sysfs_remove_groups(&bp->dev.kobj, bp->attr_group);
+ kfree(bp->attr_group);
+}
+
+static int
+ptp_ocp_attr_group_add(struct ptp_ocp *bp,
+ const struct ocp_attr_group *attr_tbl)
+{
+ int count, i;
+ int err;
+
+ count = 0;
+ for (i = 0; attr_tbl[i].cap; i++)
+ if (attr_tbl[i].cap & bp->fw_cap)
+ count++;
+
+ bp->attr_group = kcalloc(count + 1, sizeof(struct attribute_group *),
+ GFP_KERNEL);
+ if (!bp->attr_group)
+ return -ENOMEM;
+
+ count = 0;
+ for (i = 0; attr_tbl[i].cap; i++)
+ if (attr_tbl[i].cap & bp->fw_cap)
+ bp->attr_group[count++] = attr_tbl[i].group;
+
+ err = sysfs_create_groups(&bp->dev.kobj, bp->attr_group);
+ if (err)
+ bp->attr_group[0] = NULL;
+
+ return err;
+}
+
+static void
+ptp_ocp_enable_fpga(u32 __iomem *reg, u32 bit, bool enable)
+{
+ u32 ctrl;
+ bool on;
+
+ ctrl = ioread32(reg);
+ on = ctrl & bit;
+ if (on ^ enable) {
+ ctrl &= ~bit;
+ ctrl |= enable ? bit : 0;
+ iowrite32(ctrl, reg);
+ }
+}
+
+static void
+ptp_ocp_irig_out(struct ptp_ocp *bp, bool enable)
+{
+ return ptp_ocp_enable_fpga(&bp->irig_out->ctrl,
+ IRIG_M_CTRL_ENABLE, enable);
+}
+
+static void
+ptp_ocp_irig_in(struct ptp_ocp *bp, bool enable)
+{
+ return ptp_ocp_enable_fpga(&bp->irig_in->ctrl,
+ IRIG_S_CTRL_ENABLE, enable);
+}
+
+static void
+ptp_ocp_dcf_out(struct ptp_ocp *bp, bool enable)
+{
+ return ptp_ocp_enable_fpga(&bp->dcf_out->ctrl,
+ DCF_M_CTRL_ENABLE, enable);
+}
+
+static void
+ptp_ocp_dcf_in(struct ptp_ocp *bp, bool enable)
+{
+ return ptp_ocp_enable_fpga(&bp->dcf_in->ctrl,
+ DCF_S_CTRL_ENABLE, enable);
+}
+
+static void
+__handle_signal_outputs(struct ptp_ocp *bp, u32 val)
+{
+ ptp_ocp_irig_out(bp, val & 0x00100010);
+ ptp_ocp_dcf_out(bp, val & 0x00200020);
+}
+
+static void
+__handle_signal_inputs(struct ptp_ocp *bp, u32 val)
+{
+ ptp_ocp_irig_in(bp, val & 0x00100010);
+ ptp_ocp_dcf_in(bp, val & 0x00200020);
+}
+
+static u32
+ptp_ocp_sma_fb_get(struct ptp_ocp *bp, int sma_nr)
+{
+ u32 __iomem *gpio;
+ u32 shift;
+
+ if (bp->sma[sma_nr - 1].fixed_fcn)
+ return (sma_nr - 1) & 1;
+
+ if (bp->sma[sma_nr - 1].mode == SMA_MODE_IN)
+ gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1;
+ else
+ gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2;
+ shift = sma_nr & 1 ? 0 : 16;
+
+ return (ioread32(gpio) >> shift) & 0xffff;
+}
+
+static int
+ptp_ocp_sma_fb_set_output(struct ptp_ocp *bp, int sma_nr, u32 val)
+{
+ u32 reg, mask, shift;
+ unsigned long flags;
+ u32 __iomem *gpio;
+
+ gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2;
+ shift = sma_nr & 1 ? 0 : 16;
+
+ mask = 0xffff << (16 - shift);
+
+ spin_lock_irqsave(&bp->lock, flags);
+
+ reg = ioread32(gpio);
+ reg = (reg & mask) | (val << shift);
+
+ __handle_signal_outputs(bp, reg);
+
+ iowrite32(reg, gpio);
+
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return 0;
+}
+
+static int
+ptp_ocp_sma_fb_set_inputs(struct ptp_ocp *bp, int sma_nr, u32 val)
+{
+ u32 reg, mask, shift;
+ unsigned long flags;
+ u32 __iomem *gpio;
+
+ gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1;
+ shift = sma_nr & 1 ? 0 : 16;
+
+ mask = 0xffff << (16 - shift);
+
+ spin_lock_irqsave(&bp->lock, flags);
+
+ reg = ioread32(gpio);
+ reg = (reg & mask) | (val << shift);
+
+ __handle_signal_inputs(bp, reg);
+
+ iowrite32(reg, gpio);
+
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return 0;
+}
+
+static void
+ptp_ocp_sma_fb_init(struct ptp_ocp *bp)
+{
+ u32 reg;
+ int i;
+
+ /* defaults */
+ bp->sma[0].mode = SMA_MODE_IN;
+ bp->sma[1].mode = SMA_MODE_IN;
+ bp->sma[2].mode = SMA_MODE_OUT;
+ bp->sma[3].mode = SMA_MODE_OUT;
+ for (i = 0; i < 4; i++)
+ bp->sma[i].default_fcn = i & 1;
+
+ /* If no SMA1 map, the pin functions and directions are fixed. */
+ if (!bp->sma_map1) {
+ for (i = 0; i < 4; i++) {
+ bp->sma[i].fixed_fcn = true;
+ bp->sma[i].fixed_dir = true;
+ }
+ return;
+ }
+
+ /* If SMA2 GPIO output map is all 1, it is not present.
+ * This indicates the firmware has fixed direction SMA pins.
+ */
+ reg = ioread32(&bp->sma_map2->gpio2);
+ if (reg == 0xffffffff) {
+ for (i = 0; i < 4; i++)
+ bp->sma[i].fixed_dir = true;
+ } else {
+ reg = ioread32(&bp->sma_map1->gpio1);
+ bp->sma[0].mode = reg & BIT(15) ? SMA_MODE_IN : SMA_MODE_OUT;
+ bp->sma[1].mode = reg & BIT(31) ? SMA_MODE_IN : SMA_MODE_OUT;
+
+ reg = ioread32(&bp->sma_map1->gpio2);
+ bp->sma[2].mode = reg & BIT(15) ? SMA_MODE_OUT : SMA_MODE_IN;
+ bp->sma[3].mode = reg & BIT(31) ? SMA_MODE_OUT : SMA_MODE_IN;
+ }
+}
+
+static const struct ocp_sma_op ocp_fb_sma_op = {
+ .tbl = { ptp_ocp_sma_in, ptp_ocp_sma_out },
+ .init = ptp_ocp_sma_fb_init,
+ .get = ptp_ocp_sma_fb_get,
+ .set_inputs = ptp_ocp_sma_fb_set_inputs,
+ .set_output = ptp_ocp_sma_fb_set_output,
+};
+
+static int
+ptp_ocp_fb_set_pins(struct ptp_ocp *bp)
+{
+ struct ptp_pin_desc *config;
+ int i;
+
+ config = kcalloc(4, sizeof(*config), GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+
+ for (i = 0; i < 4; i++) {
+ sprintf(config[i].name, "sma%d", i + 1);
+ config[i].index = i;
+ }
+
+ bp->ptp_info.n_pins = 4;
+ bp->ptp_info.pin_config = config;
+
+ return 0;
+}
+
+static void
+ptp_ocp_fb_set_version(struct ptp_ocp *bp)
+{
+ u64 cap = OCP_CAP_BASIC;
+ u32 version;
+
+ version = ioread32(&bp->image->version);
+
+ /* if lower 16 bits are empty, this is the fw loader. */
+ if ((version & 0xffff) == 0) {
+ version = version >> 16;
+ bp->fw_loader = true;
+ }
+
+ bp->fw_tag = version >> 15;
+ bp->fw_version = version & 0x7fff;
+
+ if (bp->fw_tag) {
+ /* FPGA firmware */
+ if (version >= 5)
+ cap |= OCP_CAP_SIGNAL | OCP_CAP_FREQ;
+ } else {
+ /* SOM firmware */
+ if (version >= 19)
+ cap |= OCP_CAP_SIGNAL;
+ if (version >= 20)
+ cap |= OCP_CAP_FREQ;
+ }
+
+ bp->fw_cap = cap;
+}
+
+/* FB specific board initializers; last "resource" registered. */
+static int
+ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ int err;
+
+ bp->flash_start = 1024 * 4096;
+ bp->eeprom_map = fb_eeprom_map;
+ bp->fw_version = ioread32(&bp->image->version);
+ bp->sma_op = &ocp_fb_sma_op;
+
+ ptp_ocp_fb_set_version(bp);
+
+ ptp_ocp_tod_init(bp);
+ ptp_ocp_nmea_out_init(bp);
+ ptp_ocp_sma_init(bp);
+ ptp_ocp_signal_init(bp);
+
+ err = ptp_ocp_attr_group_add(bp, fb_timecard_groups);
+ if (err)
+ return err;
+
+ err = ptp_ocp_fb_set_pins(bp);
+ if (err)
+ return err;
+
+ return ptp_ocp_init_clock(bp);
+}
+
+static bool
+ptp_ocp_allow_irq(struct ptp_ocp *bp, struct ocp_resource *r)
+{
+ bool allow = !r->irq_vec || r->irq_vec < bp->n_irqs;
+
+ if (!allow)
+ dev_err(&bp->pdev->dev, "irq %d out of range, skipping %s\n",
+ r->irq_vec, r->name);
+ return allow;
+}
+
+static int
+ptp_ocp_register_resources(struct ptp_ocp *bp, kernel_ulong_t driver_data)
+{
+ struct ocp_resource *r, *table;
+ int err = 0;
+
+ table = (struct ocp_resource *)driver_data;
+ for (r = table; r->setup; r++) {
+ if (!ptp_ocp_allow_irq(bp, r))
+ continue;
+ err = r->setup(bp, r);
+ if (err) {
+ dev_err(&bp->pdev->dev,
+ "Could not register %s: err %d\n",
+ r->name, err);
+ break;
+ }
+ }
+ return err;
+}
+
+static ssize_t
+ptp_ocp_show_output(const struct ocp_selector *tbl, u32 val, char *buf,
+ int def_val)
+{
+ const char *name;
+ ssize_t count;
+
+ count = sysfs_emit(buf, "OUT: ");
+ name = ptp_ocp_select_name_from_val(tbl, val);
+ if (!name)
+ name = ptp_ocp_select_name_from_val(tbl, def_val);
+ count += sysfs_emit_at(buf, count, "%s\n", name);
+ return count;
+}
+
+static ssize_t
+ptp_ocp_show_inputs(const struct ocp_selector *tbl, u32 val, char *buf,
+ int def_val)
+{
+ const char *name;
+ ssize_t count;
+ int i;
+
+ count = sysfs_emit(buf, "IN: ");
+ for (i = 0; tbl[i].name; i++) {
+ if (val & tbl[i].value) {
+ name = tbl[i].name;
+ count += sysfs_emit_at(buf, count, "%s ", name);
+ }
+ }
+ if (!val && def_val >= 0) {
+ name = ptp_ocp_select_name_from_val(tbl, def_val);
+ count += sysfs_emit_at(buf, count, "%s ", name);
+ }
+ if (count)
+ count--;
+ count += sysfs_emit_at(buf, count, "\n");
+ return count;
+}
+
+static int
+sma_parse_inputs(const struct ocp_selector * const tbl[], const char *buf,
+ enum ptp_ocp_sma_mode *mode)
+{
+ int idx, count, dir;
+ char **argv;
+ int ret;
+
+ argv = argv_split(GFP_KERNEL, buf, &count);
+ if (!argv)
+ return -ENOMEM;
+
+ ret = -EINVAL;
+ if (!count)
+ goto out;
+
+ idx = 0;
+ dir = *mode == SMA_MODE_IN ? 0 : 1;
+ if (!strcasecmp("IN:", argv[0])) {
+ dir = 0;
+ idx++;
+ }
+ if (!strcasecmp("OUT:", argv[0])) {
+ dir = 1;
+ idx++;
+ }
+ *mode = dir == 0 ? SMA_MODE_IN : SMA_MODE_OUT;
+
+ ret = 0;
+ for (; idx < count; idx++)
+ ret |= ptp_ocp_select_val_from_name(tbl[dir], argv[idx]);
+ if (ret < 0)
+ ret = -EINVAL;
+
+out:
+ argv_free(argv);
+ return ret;
+}
+
+static ssize_t
+ptp_ocp_sma_show(struct ptp_ocp *bp, int sma_nr, char *buf,
+ int default_in_val, int default_out_val)
+{
+ struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1];
+ const struct ocp_selector * const *tbl;
+ u32 val;
+
+ tbl = bp->sma_op->tbl;
+ val = ptp_ocp_sma_get(bp, sma_nr) & SMA_SELECT_MASK;
+
+ if (sma->mode == SMA_MODE_IN) {
+ if (sma->disabled)
+ val = SMA_DISABLE;
+ return ptp_ocp_show_inputs(tbl[0], val, buf, default_in_val);
+ }
+
+ return ptp_ocp_show_output(tbl[1], val, buf, default_out_val);
+}
+
+static ssize_t
+sma1_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ return ptp_ocp_sma_show(bp, 1, buf, 0, 1);
+}
+
+static ssize_t
+sma2_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ return ptp_ocp_sma_show(bp, 2, buf, -1, 1);
+}
+
+static ssize_t
+sma3_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ return ptp_ocp_sma_show(bp, 3, buf, -1, 0);
+}
+
+static ssize_t
+sma4_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ return ptp_ocp_sma_show(bp, 4, buf, -1, 1);
+}
+
+static int
+ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr)
+{
+ struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1];
+ enum ptp_ocp_sma_mode mode;
+ int val;
+
+ mode = sma->mode;
+ val = sma_parse_inputs(bp->sma_op->tbl, buf, &mode);
+ if (val < 0)
+ return val;
+
+ if (sma->fixed_dir && (mode != sma->mode || val & SMA_DISABLE))
+ return -EOPNOTSUPP;
+
+ if (sma->fixed_fcn) {
+ if (val != sma->default_fcn)
+ return -EOPNOTSUPP;
+ return 0;
+ }
+
+ sma->disabled = !!(val & SMA_DISABLE);
+
+ if (mode != sma->mode) {
+ if (mode == SMA_MODE_IN)
+ ptp_ocp_sma_set_output(bp, sma_nr, 0);
+ else
+ ptp_ocp_sma_set_inputs(bp, sma_nr, 0);
+ sma->mode = mode;
+ }
+
+ if (!sma->fixed_dir)
+ val |= SMA_ENABLE; /* add enable bit */
+
+ if (sma->disabled)
+ val = 0;
+
+ if (mode == SMA_MODE_IN)
+ val = ptp_ocp_sma_set_inputs(bp, sma_nr, val);
+ else
+ val = ptp_ocp_sma_set_output(bp, sma_nr, val);
+
+ return val;
+}
+
+static ssize_t
+sma1_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int err;
+
+ err = ptp_ocp_sma_store(bp, buf, 1);
+ return err ? err : count;
+}
+
+static ssize_t
+sma2_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int err;
+
+ err = ptp_ocp_sma_store(bp, buf, 2);
+ return err ? err : count;
+}
+
+static ssize_t
+sma3_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int err;
+
+ err = ptp_ocp_sma_store(bp, buf, 3);
+ return err ? err : count;
+}
+
+static ssize_t
+sma4_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int err;
+
+ err = ptp_ocp_sma_store(bp, buf, 4);
+ return err ? err : count;
+}
+static DEVICE_ATTR_RW(sma1);
+static DEVICE_ATTR_RW(sma2);
+static DEVICE_ATTR_RW(sma3);
+static DEVICE_ATTR_RW(sma4);
+
+static ssize_t
+available_sma_inputs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ return ptp_ocp_select_table_show(bp->sma_op->tbl[0], buf);
+}
+static DEVICE_ATTR_RO(available_sma_inputs);
+
+static ssize_t
+available_sma_outputs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ return ptp_ocp_select_table_show(bp->sma_op->tbl[1], buf);
+}
+static DEVICE_ATTR_RO(available_sma_outputs);
+
+#define EXT_ATTR_RO(_group, _name, _val) \
+ struct dev_ext_attribute dev_attr_##_group##_val##_##_name = \
+ { __ATTR_RO(_name), (void *)_val }
+#define EXT_ATTR_RW(_group, _name, _val) \
+ struct dev_ext_attribute dev_attr_##_group##_val##_##_name = \
+ { __ATTR_RW(_name), (void *)_val }
+#define to_ext_attr(x) container_of(x, struct dev_ext_attribute, attr)
+
+/* period [duty [phase [polarity]]] */
+static ssize_t
+signal_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ struct ptp_ocp_signal s = { };
+ int gen = (uintptr_t)ea->var;
+ int argc, err;
+ char **argv;
+
+ argv = argv_split(GFP_KERNEL, buf, &argc);
+ if (!argv)
+ return -ENOMEM;
+
+ err = -EINVAL;
+ s.duty = bp->signal[gen].duty;
+ s.phase = bp->signal[gen].phase;
+ s.period = bp->signal[gen].period;
+ s.polarity = bp->signal[gen].polarity;
+
+ switch (argc) {
+ case 4:
+ argc--;
+ err = kstrtobool(argv[argc], &s.polarity);
+ if (err)
+ goto out;
+ fallthrough;
+ case 3:
+ argc--;
+ err = kstrtou64(argv[argc], 0, &s.phase);
+ if (err)
+ goto out;
+ fallthrough;
+ case 2:
+ argc--;
+ err = kstrtoint(argv[argc], 0, &s.duty);
+ if (err)
+ goto out;
+ fallthrough;
+ case 1:
+ argc--;
+ err = kstrtou64(argv[argc], 0, &s.period);
+ if (err)
+ goto out;
+ break;
+ default:
+ goto out;
+ }
+
+ err = ptp_ocp_signal_set(bp, gen, &s);
+ if (err)
+ goto out;
+
+ err = ptp_ocp_signal_enable(bp->signal_out[gen], gen, s.period != 0);
+
+out:
+ argv_free(argv);
+ return err ? err : count;
+}
+
+static ssize_t
+signal_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ struct ptp_ocp_signal *signal;
+ struct timespec64 ts;
+ ssize_t count;
+ int i;
+
+ i = (uintptr_t)ea->var;
+ signal = &bp->signal[i];
+
+ count = sysfs_emit(buf, "%llu %d %llu %d", signal->period,
+ signal->duty, signal->phase, signal->polarity);
+
+ ts = ktime_to_timespec64(signal->start);
+ count += sysfs_emit_at(buf, count, " %ptT TAI\n", &ts);
+
+ return count;
+}
+static EXT_ATTR_RW(signal, signal, 0);
+static EXT_ATTR_RW(signal, signal, 1);
+static EXT_ATTR_RW(signal, signal, 2);
+static EXT_ATTR_RW(signal, signal, 3);
+
+static ssize_t
+duty_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int i = (uintptr_t)ea->var;
+
+ return sysfs_emit(buf, "%d\n", bp->signal[i].duty);
+}
+static EXT_ATTR_RO(signal, duty, 0);
+static EXT_ATTR_RO(signal, duty, 1);
+static EXT_ATTR_RO(signal, duty, 2);
+static EXT_ATTR_RO(signal, duty, 3);
+
+static ssize_t
+period_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int i = (uintptr_t)ea->var;
+
+ return sysfs_emit(buf, "%llu\n", bp->signal[i].period);
+}
+static EXT_ATTR_RO(signal, period, 0);
+static EXT_ATTR_RO(signal, period, 1);
+static EXT_ATTR_RO(signal, period, 2);
+static EXT_ATTR_RO(signal, period, 3);
+
+static ssize_t
+phase_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int i = (uintptr_t)ea->var;
+
+ return sysfs_emit(buf, "%llu\n", bp->signal[i].phase);
+}
+static EXT_ATTR_RO(signal, phase, 0);
+static EXT_ATTR_RO(signal, phase, 1);
+static EXT_ATTR_RO(signal, phase, 2);
+static EXT_ATTR_RO(signal, phase, 3);
+
+static ssize_t
+polarity_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int i = (uintptr_t)ea->var;
+
+ return sysfs_emit(buf, "%d\n", bp->signal[i].polarity);
+}
+static EXT_ATTR_RO(signal, polarity, 0);
+static EXT_ATTR_RO(signal, polarity, 1);
+static EXT_ATTR_RO(signal, polarity, 2);
+static EXT_ATTR_RO(signal, polarity, 3);
+
+static ssize_t
+running_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int i = (uintptr_t)ea->var;
+
+ return sysfs_emit(buf, "%d\n", bp->signal[i].running);
+}
+static EXT_ATTR_RO(signal, running, 0);
+static EXT_ATTR_RO(signal, running, 1);
+static EXT_ATTR_RO(signal, running, 2);
+static EXT_ATTR_RO(signal, running, 3);
+
+static ssize_t
+start_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int i = (uintptr_t)ea->var;
+ struct timespec64 ts;
+
+ ts = ktime_to_timespec64(bp->signal[i].start);
+ return sysfs_emit(buf, "%llu.%lu\n", ts.tv_sec, ts.tv_nsec);
+}
+static EXT_ATTR_RO(signal, start, 0);
+static EXT_ATTR_RO(signal, start, 1);
+static EXT_ATTR_RO(signal, start, 2);
+static EXT_ATTR_RO(signal, start, 3);
+
+static ssize_t
+seconds_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int idx = (uintptr_t)ea->var;
+ u32 val;
+ int err;
+
+ err = kstrtou32(buf, 0, &val);
+ if (err)
+ return err;
+ if (val > 0xff)
+ return -EINVAL;
+
+ if (val)
+ val = (val << 8) | 0x1;
+
+ iowrite32(val, &bp->freq_in[idx]->ctrl);
+
+ return count;
+}
+
+static ssize_t
+seconds_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int idx = (uintptr_t)ea->var;
+ u32 val;
+
+ val = ioread32(&bp->freq_in[idx]->ctrl);
+ if (val & 1)
+ val = (val >> 8) & 0xff;
+ else
+ val = 0;
+
+ return sysfs_emit(buf, "%u\n", val);
+}
+static EXT_ATTR_RW(freq, seconds, 0);
+static EXT_ATTR_RW(freq, seconds, 1);
+static EXT_ATTR_RW(freq, seconds, 2);
+static EXT_ATTR_RW(freq, seconds, 3);
+
+static ssize_t
+frequency_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *ea = to_ext_attr(attr);
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int idx = (uintptr_t)ea->var;
+ u32 val;
+
+ val = ioread32(&bp->freq_in[idx]->status);
+ if (val & FREQ_STATUS_ERROR)
+ return sysfs_emit(buf, "error\n");
+ if (val & FREQ_STATUS_OVERRUN)
+ return sysfs_emit(buf, "overrun\n");
+ if (val & FREQ_STATUS_VALID)
+ return sysfs_emit(buf, "%lu\n", val & FREQ_STATUS_MASK);
+ return 0;
+}
+static EXT_ATTR_RO(freq, frequency, 0);
+static EXT_ATTR_RO(freq, frequency, 1);
+static EXT_ATTR_RO(freq, frequency, 2);
+static EXT_ATTR_RO(freq, frequency, 3);
+
+static ssize_t
+serialnum_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ if (!bp->has_eeprom_data)
+ ptp_ocp_read_eeprom(bp);
+
+ return sysfs_emit(buf, "%pM\n", bp->serial);
+}
+static DEVICE_ATTR_RO(serialnum);
+
+static ssize_t
+gnss_sync_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ ssize_t ret;
+
+ if (bp->gnss_lost)
+ ret = sysfs_emit(buf, "LOST @ %ptT\n", &bp->gnss_lost);
+ else
+ ret = sysfs_emit(buf, "SYNC\n");
+
+ return ret;
+}
+static DEVICE_ATTR_RO(gnss_sync);
+
+static ssize_t
+utc_tai_offset_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", bp->utc_tai_offset);
+}
+
+static ssize_t
+utc_tai_offset_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int err;
+ u32 val;
+
+ err = kstrtou32(buf, 0, &val);
+ if (err)
+ return err;
+
+ ptp_ocp_utc_distribute(bp, val);
+
+ return count;
+}
+static DEVICE_ATTR_RW(utc_tai_offset);
+
+static ssize_t
+ts_window_adjust_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", bp->ts_window_adjust);
+}
+
+static ssize_t
+ts_window_adjust_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ int err;
+ u32 val;
+
+ err = kstrtou32(buf, 0, &val);
+ if (err)
+ return err;
+
+ bp->ts_window_adjust = val;
+
+ return count;
+}
+static DEVICE_ATTR_RW(ts_window_adjust);
+
+static ssize_t
+irig_b_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ u32 val;
+
+ val = ioread32(&bp->irig_out->ctrl);
+ val = (val >> 16) & 0x07;
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static ssize_t
+irig_b_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ unsigned long flags;
+ int err;
+ u32 reg;
+ u8 val;
+
+ err = kstrtou8(buf, 0, &val);
+ if (err)
+ return err;
+ if (val > 7)
+ return -EINVAL;
+
+ reg = ((val & 0x7) << 16);
+
+ spin_lock_irqsave(&bp->lock, flags);
+ iowrite32(0, &bp->irig_out->ctrl); /* disable */
+ iowrite32(reg, &bp->irig_out->ctrl); /* change mode */
+ iowrite32(reg | IRIG_M_CTRL_ENABLE, &bp->irig_out->ctrl);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return count;
+}
+static DEVICE_ATTR_RW(irig_b_mode);
+
+static ssize_t
+clock_source_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ const char *p;
+ u32 select;
+
+ select = ioread32(&bp->reg->select);
+ p = ptp_ocp_select_name_from_val(ptp_ocp_clock, select >> 16);
+
+ return sysfs_emit(buf, "%s\n", p);
+}
+
+static ssize_t
+clock_source_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ unsigned long flags;
+ int val;
+
+ val = ptp_ocp_select_val_from_name(ptp_ocp_clock, buf);
+ if (val < 0)
+ return val;
+
+ spin_lock_irqsave(&bp->lock, flags);
+ iowrite32(val, &bp->reg->select);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return count;
+}
+static DEVICE_ATTR_RW(clock_source);
+
+static ssize_t
+available_clock_sources_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return ptp_ocp_select_table_show(ptp_ocp_clock, buf);
+}
+static DEVICE_ATTR_RO(available_clock_sources);
+
+static ssize_t
+clock_status_drift_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ u32 val;
+ int res;
+
+ val = ioread32(&bp->reg->status_drift);
+ res = (val & ~INT_MAX) ? -1 : 1;
+ res *= (val & INT_MAX);
+ return sysfs_emit(buf, "%d\n", res);
+}
+static DEVICE_ATTR_RO(clock_status_drift);
+
+static ssize_t
+clock_status_offset_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ u32 val;
+ int res;
+
+ val = ioread32(&bp->reg->status_offset);
+ res = (val & ~INT_MAX) ? -1 : 1;
+ res *= (val & INT_MAX);
+ return sysfs_emit(buf, "%d\n", res);
+}
+static DEVICE_ATTR_RO(clock_status_offset);
+
+static ssize_t
+tod_correction_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ u32 val;
+ int res;
+
+ val = ioread32(&bp->tod->adj_sec);
+ res = (val & ~INT_MAX) ? -1 : 1;
+ res *= (val & INT_MAX);
+ return sysfs_emit(buf, "%d\n", res);
+}
+
+static ssize_t
+tod_correction_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ unsigned long flags;
+ int err, res;
+ u32 val = 0;
+
+ err = kstrtos32(buf, 0, &res);
+ if (err)
+ return err;
+ if (res < 0) {
+ res *= -1;
+ val |= BIT(31);
+ }
+ val |= res;
+
+ spin_lock_irqsave(&bp->lock, flags);
+ iowrite32(val, &bp->tod->adj_sec);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return count;
+}
+static DEVICE_ATTR_RW(tod_correction);
+
+#define _DEVICE_SIGNAL_GROUP_ATTRS(_nr) \
+ static struct attribute *fb_timecard_signal##_nr##_attrs[] = { \
+ &dev_attr_signal##_nr##_signal.attr.attr, \
+ &dev_attr_signal##_nr##_duty.attr.attr, \
+ &dev_attr_signal##_nr##_phase.attr.attr, \
+ &dev_attr_signal##_nr##_period.attr.attr, \
+ &dev_attr_signal##_nr##_polarity.attr.attr, \
+ &dev_attr_signal##_nr##_running.attr.attr, \
+ &dev_attr_signal##_nr##_start.attr.attr, \
+ NULL, \
+ }
+
+#define DEVICE_SIGNAL_GROUP(_name, _nr) \
+ _DEVICE_SIGNAL_GROUP_ATTRS(_nr); \
+ static const struct attribute_group \
+ fb_timecard_signal##_nr##_group = { \
+ .name = #_name, \
+ .attrs = fb_timecard_signal##_nr##_attrs, \
+}
+
+DEVICE_SIGNAL_GROUP(gen1, 0);
+DEVICE_SIGNAL_GROUP(gen2, 1);
+DEVICE_SIGNAL_GROUP(gen3, 2);
+DEVICE_SIGNAL_GROUP(gen4, 3);
+
+#define _DEVICE_FREQ_GROUP_ATTRS(_nr) \
+ static struct attribute *fb_timecard_freq##_nr##_attrs[] = { \
+ &dev_attr_freq##_nr##_seconds.attr.attr, \
+ &dev_attr_freq##_nr##_frequency.attr.attr, \
+ NULL, \
+ }
+
+#define DEVICE_FREQ_GROUP(_name, _nr) \
+ _DEVICE_FREQ_GROUP_ATTRS(_nr); \
+ static const struct attribute_group \
+ fb_timecard_freq##_nr##_group = { \
+ .name = #_name, \
+ .attrs = fb_timecard_freq##_nr##_attrs, \
+}
+
+DEVICE_FREQ_GROUP(freq1, 0);
+DEVICE_FREQ_GROUP(freq2, 1);
+DEVICE_FREQ_GROUP(freq3, 2);
+DEVICE_FREQ_GROUP(freq4, 3);
+
+static struct attribute *fb_timecard_attrs[] = {
+ &dev_attr_serialnum.attr,
+ &dev_attr_gnss_sync.attr,
+ &dev_attr_clock_source.attr,
+ &dev_attr_available_clock_sources.attr,
+ &dev_attr_sma1.attr,
+ &dev_attr_sma2.attr,
+ &dev_attr_sma3.attr,
+ &dev_attr_sma4.attr,
+ &dev_attr_available_sma_inputs.attr,
+ &dev_attr_available_sma_outputs.attr,
+ &dev_attr_clock_status_drift.attr,
+ &dev_attr_clock_status_offset.attr,
+ &dev_attr_irig_b_mode.attr,
+ &dev_attr_utc_tai_offset.attr,
+ &dev_attr_ts_window_adjust.attr,
+ &dev_attr_tod_correction.attr,
+ NULL,
+};
+static const struct attribute_group fb_timecard_group = {
+ .attrs = fb_timecard_attrs,
+};
+static const struct ocp_attr_group fb_timecard_groups[] = {
+ { .cap = OCP_CAP_BASIC, .group = &fb_timecard_group },
+ { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal0_group },
+ { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal1_group },
+ { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal2_group },
+ { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal3_group },
+ { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq0_group },
+ { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq1_group },
+ { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq2_group },
+ { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq3_group },
+ { },
+};
+
+static void
+gpio_input_map(char *buf, struct ptp_ocp *bp, u16 map[][2], u16 bit,
+ const char *def)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (bp->sma[i].mode != SMA_MODE_IN)
+ continue;
+ if (map[i][0] & (1 << bit)) {
+ sprintf(buf, "sma%d", i + 1);
+ return;
+ }
+ }
+ if (!def)
+ def = "----";
+ strcpy(buf, def);
+}
+
+static void
+gpio_output_map(char *buf, struct ptp_ocp *bp, u16 map[][2], u16 bit)
+{
+ char *ans = buf;
+ int i;
+
+ strcpy(ans, "----");
+ for (i = 0; i < 4; i++) {
+ if (bp->sma[i].mode != SMA_MODE_OUT)
+ continue;
+ if (map[i][1] & (1 << bit))
+ ans += sprintf(ans, "sma%d ", i + 1);
+ }
+}
+
+static void
+_signal_summary_show(struct seq_file *s, struct ptp_ocp *bp, int nr)
+{
+ struct signal_reg __iomem *reg = bp->signal_out[nr]->mem;
+ struct ptp_ocp_signal *signal = &bp->signal[nr];
+ char label[8];
+ bool on;
+ u32 val;
+
+ if (!signal)
+ return;
+
+ on = signal->running;
+ sprintf(label, "GEN%d", nr + 1);
+ seq_printf(s, "%7s: %s, period:%llu duty:%d%% phase:%llu pol:%d",
+ label, on ? " ON" : "OFF",
+ signal->period, signal->duty, signal->phase,
+ signal->polarity);
+
+ val = ioread32(&reg->enable);
+ seq_printf(s, " [%x", val);
+ val = ioread32(&reg->status);
+ seq_printf(s, " %x]", val);
+
+ seq_printf(s, " start:%llu\n", signal->start);
+}
+
+static void
+_frequency_summary_show(struct seq_file *s, int nr,
+ struct frequency_reg __iomem *reg)
+{
+ char label[8];
+ bool on;
+ u32 val;
+
+ if (!reg)
+ return;
+
+ sprintf(label, "FREQ%d", nr + 1);
+ val = ioread32(&reg->ctrl);
+ on = val & 1;
+ val = (val >> 8) & 0xff;
+ seq_printf(s, "%7s: %s, sec:%u",
+ label,
+ on ? " ON" : "OFF",
+ val);
+
+ val = ioread32(&reg->status);
+ if (val & FREQ_STATUS_ERROR)
+ seq_printf(s, ", error");
+ if (val & FREQ_STATUS_OVERRUN)
+ seq_printf(s, ", overrun");
+ if (val & FREQ_STATUS_VALID)
+ seq_printf(s, ", freq %lu Hz", val & FREQ_STATUS_MASK);
+ seq_printf(s, " reg:%x\n", val);
+}
+
+static int
+ptp_ocp_summary_show(struct seq_file *s, void *data)
+{
+ struct device *dev = s->private;
+ struct ptp_system_timestamp sts;
+ struct ts_reg __iomem *ts_reg;
+ char *buf, *src, *mac_src;
+ struct timespec64 ts;
+ struct ptp_ocp *bp;
+ u16 sma_val[4][2];
+ u32 ctrl, val;
+ bool on, map;
+ int i;
+
+ buf = (char *)__get_free_page(GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ bp = dev_get_drvdata(dev);
+
+ seq_printf(s, "%7s: /dev/ptp%d\n", "PTP", ptp_clock_index(bp->ptp));
+ if (bp->gnss_port != -1)
+ seq_printf(s, "%7s: /dev/ttyS%d\n", "GNSS1", bp->gnss_port);
+ if (bp->gnss2_port != -1)
+ seq_printf(s, "%7s: /dev/ttyS%d\n", "GNSS2", bp->gnss2_port);
+ if (bp->mac_port != -1)
+ seq_printf(s, "%7s: /dev/ttyS%d\n", "MAC", bp->mac_port);
+ if (bp->nmea_port != -1)
+ seq_printf(s, "%7s: /dev/ttyS%d\n", "NMEA", bp->nmea_port);
+
+ memset(sma_val, 0xff, sizeof(sma_val));
+ if (bp->sma_map1) {
+ u32 reg;
+
+ reg = ioread32(&bp->sma_map1->gpio1);
+ sma_val[0][0] = reg & 0xffff;
+ sma_val[1][0] = reg >> 16;
+
+ reg = ioread32(&bp->sma_map1->gpio2);
+ sma_val[2][1] = reg & 0xffff;
+ sma_val[3][1] = reg >> 16;
+
+ reg = ioread32(&bp->sma_map2->gpio1);
+ sma_val[2][0] = reg & 0xffff;
+ sma_val[3][0] = reg >> 16;
+
+ reg = ioread32(&bp->sma_map2->gpio2);
+ sma_val[0][1] = reg & 0xffff;
+ sma_val[1][1] = reg >> 16;
+ }
+
+ sma1_show(dev, NULL, buf);
+ seq_printf(s, " sma1: %04x,%04x %s",
+ sma_val[0][0], sma_val[0][1], buf);
+
+ sma2_show(dev, NULL, buf);
+ seq_printf(s, " sma2: %04x,%04x %s",
+ sma_val[1][0], sma_val[1][1], buf);
+
+ sma3_show(dev, NULL, buf);
+ seq_printf(s, " sma3: %04x,%04x %s",
+ sma_val[2][0], sma_val[2][1], buf);
+
+ sma4_show(dev, NULL, buf);
+ seq_printf(s, " sma4: %04x,%04x %s",
+ sma_val[3][0], sma_val[3][1], buf);
+
+ if (bp->ts0) {
+ ts_reg = bp->ts0->mem;
+ on = ioread32(&ts_reg->enable);
+ src = "GNSS1";
+ seq_printf(s, "%7s: %s, src: %s\n", "TS0",
+ on ? " ON" : "OFF", src);
+ }
+
+ if (bp->ts1) {
+ ts_reg = bp->ts1->mem;
+ on = ioread32(&ts_reg->enable);
+ gpio_input_map(buf, bp, sma_val, 2, NULL);
+ seq_printf(s, "%7s: %s, src: %s\n", "TS1",
+ on ? " ON" : "OFF", buf);
+ }
+
+ if (bp->ts2) {
+ ts_reg = bp->ts2->mem;
+ on = ioread32(&ts_reg->enable);
+ gpio_input_map(buf, bp, sma_val, 3, NULL);
+ seq_printf(s, "%7s: %s, src: %s\n", "TS2",
+ on ? " ON" : "OFF", buf);
+ }
+
+ if (bp->ts3) {
+ ts_reg = bp->ts3->mem;
+ on = ioread32(&ts_reg->enable);
+ gpio_input_map(buf, bp, sma_val, 6, NULL);
+ seq_printf(s, "%7s: %s, src: %s\n", "TS3",
+ on ? " ON" : "OFF", buf);
+ }
+
+ if (bp->ts4) {
+ ts_reg = bp->ts4->mem;
+ on = ioread32(&ts_reg->enable);
+ gpio_input_map(buf, bp, sma_val, 7, NULL);
+ seq_printf(s, "%7s: %s, src: %s\n", "TS4",
+ on ? " ON" : "OFF", buf);
+ }
+
+ if (bp->pps) {
+ ts_reg = bp->pps->mem;
+ src = "PHC";
+ on = ioread32(&ts_reg->enable);
+ map = !!(bp->pps_req_map & OCP_REQ_TIMESTAMP);
+ seq_printf(s, "%7s: %s, src: %s\n", "TS5",
+ on && map ? " ON" : "OFF", src);
+
+ map = !!(bp->pps_req_map & OCP_REQ_PPS);
+ seq_printf(s, "%7s: %s, src: %s\n", "PPS",
+ on && map ? " ON" : "OFF", src);
+ }
+
+ if (bp->fw_cap & OCP_CAP_SIGNAL)
+ for (i = 0; i < 4; i++)
+ _signal_summary_show(s, bp, i);
+
+ if (bp->fw_cap & OCP_CAP_FREQ)
+ for (i = 0; i < 4; i++)
+ _frequency_summary_show(s, i, bp->freq_in[i]);
+
+ if (bp->irig_out) {
+ ctrl = ioread32(&bp->irig_out->ctrl);
+ on = ctrl & IRIG_M_CTRL_ENABLE;
+ val = ioread32(&bp->irig_out->status);
+ gpio_output_map(buf, bp, sma_val, 4);
+ seq_printf(s, "%7s: %s, error: %d, mode %d, out: %s\n", "IRIG",
+ on ? " ON" : "OFF", val, (ctrl >> 16), buf);
+ }
+
+ if (bp->irig_in) {
+ on = ioread32(&bp->irig_in->ctrl) & IRIG_S_CTRL_ENABLE;
+ val = ioread32(&bp->irig_in->status);
+ gpio_input_map(buf, bp, sma_val, 4, NULL);
+ seq_printf(s, "%7s: %s, error: %d, src: %s\n", "IRIG in",
+ on ? " ON" : "OFF", val, buf);
+ }
+
+ if (bp->dcf_out) {
+ on = ioread32(&bp->dcf_out->ctrl) & DCF_M_CTRL_ENABLE;
+ val = ioread32(&bp->dcf_out->status);
+ gpio_output_map(buf, bp, sma_val, 5);
+ seq_printf(s, "%7s: %s, error: %d, out: %s\n", "DCF",
+ on ? " ON" : "OFF", val, buf);
+ }
+
+ if (bp->dcf_in) {
+ on = ioread32(&bp->dcf_in->ctrl) & DCF_S_CTRL_ENABLE;
+ val = ioread32(&bp->dcf_in->status);
+ gpio_input_map(buf, bp, sma_val, 5, NULL);
+ seq_printf(s, "%7s: %s, error: %d, src: %s\n", "DCF in",
+ on ? " ON" : "OFF", val, buf);
+ }
+
+ if (bp->nmea_out) {
+ on = ioread32(&bp->nmea_out->ctrl) & 1;
+ val = ioread32(&bp->nmea_out->status);
+ seq_printf(s, "%7s: %s, error: %d\n", "NMEA",
+ on ? " ON" : "OFF", val);
+ }
+
+ /* compute src for PPS1, used below. */
+ if (bp->pps_select) {
+ val = ioread32(&bp->pps_select->gpio1);
+ src = &buf[80];
+ mac_src = "GNSS1";
+ if (val & 0x01) {
+ gpio_input_map(src, bp, sma_val, 0, NULL);
+ mac_src = src;
+ } else if (val & 0x02) {
+ src = "MAC";
+ } else if (val & 0x04) {
+ src = "GNSS1";
+ } else {
+ src = "----";
+ mac_src = src;
+ }
+ } else {
+ src = "?";
+ mac_src = src;
+ }
+ seq_printf(s, "MAC PPS1 src: %s\n", mac_src);
+
+ gpio_input_map(buf, bp, sma_val, 1, "GNSS2");
+ seq_printf(s, "MAC PPS2 src: %s\n", buf);
+
+ /* assumes automatic switchover/selection */
+ val = ioread32(&bp->reg->select);
+ switch (val >> 16) {
+ case 0:
+ sprintf(buf, "----");
+ break;
+ case 2:
+ sprintf(buf, "IRIG");
+ break;
+ case 3:
+ sprintf(buf, "%s via PPS1", src);
+ break;
+ case 6:
+ sprintf(buf, "DCF");
+ break;
+ default:
+ strcpy(buf, "unknown");
+ break;
+ }
+ val = ioread32(&bp->reg->status);
+ seq_printf(s, "%7s: %s, state: %s\n", "PHC src", buf,
+ val & OCP_STATUS_IN_SYNC ? "sync" : "unsynced");
+
+ if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, &sts)) {
+ struct timespec64 sys_ts;
+ s64 pre_ns, post_ns, ns;
+
+ pre_ns = timespec64_to_ns(&sts.pre_ts);
+ post_ns = timespec64_to_ns(&sts.post_ts);
+ ns = (pre_ns + post_ns) / 2;
+ ns += (s64)bp->utc_tai_offset * NSEC_PER_SEC;
+ sys_ts = ns_to_timespec64(ns);
+
+ seq_printf(s, "%7s: %lld.%ld == %ptT TAI\n", "PHC",
+ ts.tv_sec, ts.tv_nsec, &ts);
+ seq_printf(s, "%7s: %lld.%ld == %ptT UTC offset %d\n", "SYS",
+ sys_ts.tv_sec, sys_ts.tv_nsec, &sys_ts,
+ bp->utc_tai_offset);
+ seq_printf(s, "%7s: PHC:SYS offset: %lld window: %lld\n", "",
+ timespec64_to_ns(&ts) - ns,
+ post_ns - pre_ns);
+ }
+
+ free_page((unsigned long)buf);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ptp_ocp_summary);
+
+static int
+ptp_ocp_tod_status_show(struct seq_file *s, void *data)
+{
+ struct device *dev = s->private;
+ struct ptp_ocp *bp;
+ u32 val;
+ int idx;
+
+ bp = dev_get_drvdata(dev);
+
+ val = ioread32(&bp->tod->ctrl);
+ if (!(val & TOD_CTRL_ENABLE)) {
+ seq_printf(s, "TOD Slave disabled\n");
+ return 0;
+ }
+ seq_printf(s, "TOD Slave enabled, Control Register 0x%08X\n", val);
+
+ idx = val & TOD_CTRL_PROTOCOL ? 4 : 0;
+ idx += (val >> 16) & 3;
+ seq_printf(s, "Protocol %s\n", ptp_ocp_tod_proto_name(idx));
+
+ idx = (val >> TOD_CTRL_GNSS_SHIFT) & TOD_CTRL_GNSS_MASK;
+ seq_printf(s, "GNSS %s\n", ptp_ocp_tod_gnss_name(idx));
+
+ val = ioread32(&bp->tod->version);
+ seq_printf(s, "TOD Version %d.%d.%d\n",
+ val >> 24, (val >> 16) & 0xff, val & 0xffff);
+
+ val = ioread32(&bp->tod->status);
+ seq_printf(s, "Status register: 0x%08X\n", val);
+
+ val = ioread32(&bp->tod->adj_sec);
+ idx = (val & ~INT_MAX) ? -1 : 1;
+ idx *= (val & INT_MAX);
+ seq_printf(s, "Correction seconds: %d\n", idx);
+
+ val = ioread32(&bp->tod->utc_status);
+ seq_printf(s, "UTC status register: 0x%08X\n", val);
+ seq_printf(s, "UTC offset: %ld valid:%d\n",
+ val & TOD_STATUS_UTC_MASK, val & TOD_STATUS_UTC_VALID ? 1 : 0);
+ seq_printf(s, "Leap second info valid:%d, Leap second announce %d\n",
+ val & TOD_STATUS_LEAP_VALID ? 1 : 0,
+ val & TOD_STATUS_LEAP_ANNOUNCE ? 1 : 0);
+
+ val = ioread32(&bp->tod->leap);
+ seq_printf(s, "Time to next leap second (in sec): %d\n", (s32) val);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(ptp_ocp_tod_status);
+
+static struct dentry *ptp_ocp_debugfs_root;
+
+static void
+ptp_ocp_debugfs_add_device(struct ptp_ocp *bp)
+{
+ struct dentry *d;
+
+ d = debugfs_create_dir(dev_name(&bp->dev), ptp_ocp_debugfs_root);
+ bp->debug_root = d;
+ debugfs_create_file("summary", 0444, bp->debug_root,
+ &bp->dev, &ptp_ocp_summary_fops);
+ if (bp->tod)
+ debugfs_create_file("tod_status", 0444, bp->debug_root,
+ &bp->dev, &ptp_ocp_tod_status_fops);
+}
+
+static void
+ptp_ocp_debugfs_remove_device(struct ptp_ocp *bp)
+{
+ debugfs_remove_recursive(bp->debug_root);
+}
+
+static void
+ptp_ocp_debugfs_init(void)
+{
+ ptp_ocp_debugfs_root = debugfs_create_dir("timecard", NULL);
+}
+
+static void
+ptp_ocp_debugfs_fini(void)
+{
+ debugfs_remove_recursive(ptp_ocp_debugfs_root);
+}
+
+static void
+ptp_ocp_dev_release(struct device *dev)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ mutex_lock(&ptp_ocp_lock);
+ idr_remove(&ptp_ocp_idr, bp->id);
+ mutex_unlock(&ptp_ocp_lock);
+}
+
+static int
+ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev)
+{
+ int err;
+
+ mutex_lock(&ptp_ocp_lock);
+ err = idr_alloc(&ptp_ocp_idr, bp, 0, 0, GFP_KERNEL);
+ mutex_unlock(&ptp_ocp_lock);
+ if (err < 0) {
+ dev_err(&pdev->dev, "idr_alloc failed: %d\n", err);
+ return err;
+ }
+ bp->id = err;
+
+ bp->ptp_info = ptp_ocp_clock_info;
+ spin_lock_init(&bp->lock);
+ bp->gnss_port = -1;
+ bp->gnss2_port = -1;
+ bp->mac_port = -1;
+ bp->nmea_port = -1;
+ bp->pdev = pdev;
+
+ device_initialize(&bp->dev);
+ dev_set_name(&bp->dev, "ocp%d", bp->id);
+ bp->dev.class = &timecard_class;
+ bp->dev.parent = &pdev->dev;
+ bp->dev.release = ptp_ocp_dev_release;
+ dev_set_drvdata(&bp->dev, bp);
+
+ err = device_add(&bp->dev);
+ if (err) {
+ dev_err(&bp->dev, "device add failed: %d\n", err);
+ goto out;
+ }
+
+ pci_set_drvdata(pdev, bp);
+
+ return 0;
+
+out:
+ ptp_ocp_dev_release(&bp->dev);
+ put_device(&bp->dev);
+ return err;
+}
+
+static void
+ptp_ocp_symlink(struct ptp_ocp *bp, struct device *child, const char *link)
+{
+ struct device *dev = &bp->dev;
+
+ if (sysfs_create_link(&dev->kobj, &child->kobj, link))
+ dev_err(dev, "%s symlink failed\n", link);
+}
+
+static void
+ptp_ocp_link_child(struct ptp_ocp *bp, const char *name, const char *link)
+{
+ struct device *dev, *child;
+
+ dev = &bp->pdev->dev;
+
+ child = device_find_child_by_name(dev, name);
+ if (!child) {
+ dev_err(dev, "Could not find device %s\n", name);
+ return;
+ }
+
+ ptp_ocp_symlink(bp, child, link);
+ put_device(child);
+}
+
+static int
+ptp_ocp_complete(struct ptp_ocp *bp)
+{
+ struct pps_device *pps;
+ char buf[32];
+
+ if (bp->gnss_port != -1) {
+ sprintf(buf, "ttyS%d", bp->gnss_port);
+ ptp_ocp_link_child(bp, buf, "ttyGNSS");
+ }
+ if (bp->gnss2_port != -1) {
+ sprintf(buf, "ttyS%d", bp->gnss2_port);
+ ptp_ocp_link_child(bp, buf, "ttyGNSS2");
+ }
+ if (bp->mac_port != -1) {
+ sprintf(buf, "ttyS%d", bp->mac_port);
+ ptp_ocp_link_child(bp, buf, "ttyMAC");
+ }
+ if (bp->nmea_port != -1) {
+ sprintf(buf, "ttyS%d", bp->nmea_port);
+ ptp_ocp_link_child(bp, buf, "ttyNMEA");
+ }
+ sprintf(buf, "ptp%d", ptp_clock_index(bp->ptp));
+ ptp_ocp_link_child(bp, buf, "ptp");
+
+ pps = pps_lookup_dev(bp->ptp);
+ if (pps)
+ ptp_ocp_symlink(bp, pps->dev, "pps");
+
+ ptp_ocp_debugfs_add_device(bp);
+
+ return 0;
+}
+
+static void
+ptp_ocp_phc_info(struct ptp_ocp *bp)
+{
+ struct timespec64 ts;
+ u32 version, select;
+ bool sync;
+
+ version = ioread32(&bp->reg->version);
+ select = ioread32(&bp->reg->select);
+ dev_info(&bp->pdev->dev, "Version %d.%d.%d, clock %s, device ptp%d\n",
+ version >> 24, (version >> 16) & 0xff, version & 0xffff,
+ ptp_ocp_select_name_from_val(ptp_ocp_clock, select >> 16),
+ ptp_clock_index(bp->ptp));
+
+ sync = ioread32(&bp->reg->status) & OCP_STATUS_IN_SYNC;
+ if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, NULL))
+ dev_info(&bp->pdev->dev, "Time: %lld.%ld, %s\n",
+ ts.tv_sec, ts.tv_nsec,
+ sync ? "in-sync" : "UNSYNCED");
+}
+
+static void
+ptp_ocp_serial_info(struct device *dev, const char *name, int port, int baud)
+{
+ if (port != -1)
+ dev_info(dev, "%5s: /dev/ttyS%-2d @ %6d\n", name, port, baud);
+}
+
+static void
+ptp_ocp_info(struct ptp_ocp *bp)
+{
+ static int nmea_baud[] = {
+ 1200, 2400, 4800, 9600, 19200, 38400,
+ 57600, 115200, 230400, 460800, 921600,
+ 1000000, 2000000
+ };
+ struct device *dev = &bp->pdev->dev;
+ u32 reg;
+
+ ptp_ocp_phc_info(bp);
+
+ ptp_ocp_serial_info(dev, "GNSS", bp->gnss_port, 115200);
+ ptp_ocp_serial_info(dev, "GNSS2", bp->gnss2_port, 115200);
+ ptp_ocp_serial_info(dev, "MAC", bp->mac_port, 57600);
+ if (bp->nmea_out && bp->nmea_port != -1) {
+ int baud = -1;
+
+ reg = ioread32(&bp->nmea_out->uart_baud);
+ if (reg < ARRAY_SIZE(nmea_baud))
+ baud = nmea_baud[reg];
+ ptp_ocp_serial_info(dev, "NMEA", bp->nmea_port, baud);
+ }
+}
+
+static void
+ptp_ocp_detach_sysfs(struct ptp_ocp *bp)
+{
+ struct device *dev = &bp->dev;
+
+ sysfs_remove_link(&dev->kobj, "ttyGNSS");
+ sysfs_remove_link(&dev->kobj, "ttyGNSS2");
+ sysfs_remove_link(&dev->kobj, "ttyMAC");
+ sysfs_remove_link(&dev->kobj, "ptp");
+ sysfs_remove_link(&dev->kobj, "pps");
+}
+
+static void
+ptp_ocp_detach(struct ptp_ocp *bp)
+{
+ int i;
+
+ ptp_ocp_debugfs_remove_device(bp);
+ ptp_ocp_detach_sysfs(bp);
+ ptp_ocp_attr_group_del(bp);
+ if (timer_pending(&bp->watchdog))
+ del_timer_sync(&bp->watchdog);
+ if (bp->ts0)
+ ptp_ocp_unregister_ext(bp->ts0);
+ if (bp->ts1)
+ ptp_ocp_unregister_ext(bp->ts1);
+ if (bp->ts2)
+ ptp_ocp_unregister_ext(bp->ts2);
+ if (bp->ts3)
+ ptp_ocp_unregister_ext(bp->ts3);
+ if (bp->ts4)
+ ptp_ocp_unregister_ext(bp->ts4);
+ if (bp->pps)
+ ptp_ocp_unregister_ext(bp->pps);
+ for (i = 0; i < 4; i++)
+ if (bp->signal_out[i])
+ ptp_ocp_unregister_ext(bp->signal_out[i]);
+ if (bp->gnss_port != -1)
+ serial8250_unregister_port(bp->gnss_port);
+ if (bp->gnss2_port != -1)
+ serial8250_unregister_port(bp->gnss2_port);
+ if (bp->mac_port != -1)
+ serial8250_unregister_port(bp->mac_port);
+ if (bp->nmea_port != -1)
+ serial8250_unregister_port(bp->nmea_port);
+ platform_device_unregister(bp->spi_flash);
+ platform_device_unregister(bp->i2c_ctrl);
+ if (bp->i2c_clk)
+ clk_hw_unregister_fixed_rate(bp->i2c_clk);
+ if (bp->n_irqs)
+ pci_free_irq_vectors(bp->pdev);
+ if (bp->ptp)
+ ptp_clock_unregister(bp->ptp);
+ kfree(bp->ptp_info.pin_config);
+ device_unregister(&bp->dev);
+}
+
+static int
+ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct devlink *devlink;
+ struct ptp_ocp *bp;
+ int err;
+
+ devlink = devlink_alloc(&ptp_ocp_devlink_ops, sizeof(*bp), &pdev->dev);
+ if (!devlink) {
+ dev_err(&pdev->dev, "devlink_alloc failed\n");
+ return -ENOMEM;
+ }
+
+ err = pci_enable_device(pdev);
+ if (err) {
+ dev_err(&pdev->dev, "pci_enable_device\n");
+ goto out_free;
+ }
+
+ bp = devlink_priv(devlink);
+ err = ptp_ocp_device_init(bp, pdev);
+ if (err)
+ goto out_disable;
+
+ /* compat mode.
+ * Older FPGA firmware only returns 2 irq's.
+ * allow this - if not all of the IRQ's are returned, skip the
+ * extra devices and just register the clock.
+ */
+ err = pci_alloc_irq_vectors(pdev, 1, 17, PCI_IRQ_MSI | PCI_IRQ_MSIX);
+ if (err < 0) {
+ dev_err(&pdev->dev, "alloc_irq_vectors err: %d\n", err);
+ goto out;
+ }
+ bp->n_irqs = err;
+ pci_set_master(pdev);
+
+ err = ptp_ocp_register_resources(bp, id->driver_data);
+ if (err)
+ goto out;
+
+ bp->ptp = ptp_clock_register(&bp->ptp_info, &pdev->dev);
+ if (IS_ERR(bp->ptp)) {
+ err = PTR_ERR(bp->ptp);
+ dev_err(&pdev->dev, "ptp_clock_register: %d\n", err);
+ bp->ptp = NULL;
+ goto out;
+ }
+
+ err = ptp_ocp_complete(bp);
+ if (err)
+ goto out;
+
+ ptp_ocp_info(bp);
+ devlink_register(devlink);
+ return 0;
+
+out:
+ ptp_ocp_detach(bp);
+out_disable:
+ pci_disable_device(pdev);
+out_free:
+ devlink_free(devlink);
+ return err;
+}
+
+static void
+ptp_ocp_remove(struct pci_dev *pdev)
+{
+ struct ptp_ocp *bp = pci_get_drvdata(pdev);
+ struct devlink *devlink = priv_to_devlink(bp);
+
+ devlink_unregister(devlink);
+ ptp_ocp_detach(bp);
+ pci_disable_device(pdev);
+
+ devlink_free(devlink);
+}
+
+static struct pci_driver ptp_ocp_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = ptp_ocp_pcidev_id,
+ .probe = ptp_ocp_probe,
+ .remove = ptp_ocp_remove,
+};
+
+static int
+ptp_ocp_i2c_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct device *dev, *child = data;
+ struct ptp_ocp *bp;
+ bool add;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ case BUS_NOTIFY_DEL_DEVICE:
+ add = action == BUS_NOTIFY_ADD_DEVICE;
+ break;
+ default:
+ return 0;
+ }
+
+ if (!i2c_verify_adapter(child))
+ return 0;
+
+ dev = child;
+ while ((dev = dev->parent))
+ if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME))
+ goto found;
+ return 0;
+
+found:
+ bp = dev_get_drvdata(dev);
+ if (add)
+ ptp_ocp_symlink(bp, child, "i2c");
+ else
+ sysfs_remove_link(&bp->dev.kobj, "i2c");
+
+ return 0;
+}
+
+static struct notifier_block ptp_ocp_i2c_notifier = {
+ .notifier_call = ptp_ocp_i2c_notifier_call,
+};
+
+static int __init
+ptp_ocp_init(void)
+{
+ const char *what;
+ int err;
+
+ ptp_ocp_debugfs_init();
+
+ what = "timecard class";
+ err = class_register(&timecard_class);
+ if (err)
+ goto out;
+
+ what = "i2c notifier";
+ err = bus_register_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
+ if (err)
+ goto out_notifier;
+
+ what = "ptp_ocp driver";
+ err = pci_register_driver(&ptp_ocp_driver);
+ if (err)
+ goto out_register;
+
+ return 0;
+
+out_register:
+ bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
+out_notifier:
+ class_unregister(&timecard_class);
+out:
+ ptp_ocp_debugfs_fini();
+ pr_err(KBUILD_MODNAME ": failed to register %s: %d\n", what, err);
+ return err;
+}
+
+static void __exit
+ptp_ocp_fini(void)
+{
+ bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
+ pci_unregister_driver(&ptp_ocp_driver);
+ class_unregister(&timecard_class);
+ ptp_ocp_debugfs_fini();
+}
+
+module_init(ptp_ocp_init);
+module_exit(ptp_ocp_fini);
+
+MODULE_DESCRIPTION("OpenCompute TimeCard driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/ptp/ptp_pch.c b/drivers/ptp/ptp_pch.c
index dcd6e00c8046..7d4da9e605ef 100644
--- a/drivers/ptp/ptp_pch.c
+++ b/drivers/ptp/ptp_pch.c
@@ -10,14 +10,16 @@
#include <linux/device.h>
#include <linux/err.h>
-#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/io-64-nonatomic-hi-lo.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/ptp_clock_kernel.h>
+#include <linux/ptp_pch.h>
#include <linux/slab.h>
#define STATION_ADDR_LEN 20
@@ -36,7 +38,8 @@ enum pch_status {
PCH_FAILED,
PCH_UNSUPPORTED,
};
-/**
+
+/*
* struct pch_ts_regs - IEEE 1588 registers
*/
struct pch_ts_regs {
@@ -98,11 +101,11 @@ struct pch_ts_regs {
#define PCH_ECS_ETH (1 << 0)
#define PCH_ECS_CAN (1 << 1)
-#define PCH_STATION_BYTES 6
#define PCH_IEEE1588_ETH (1 << 0)
#define PCH_IEEE1588_CAN (1 << 1)
-/**
+
+/*
* struct pch_dev - Driver private data
*/
struct pch_dev {
@@ -112,14 +115,12 @@ struct pch_dev {
int exts0_enabled;
int exts1_enabled;
- u32 mem_base;
- u32 mem_size;
u32 irq;
struct pci_dev *pdev;
spinlock_t register_lock;
};
-/**
+/*
* struct pch_params - 1588 module parameter
*/
struct pch_params {
@@ -145,28 +146,15 @@ static inline void pch_eth_enable_set(struct pch_dev *chip)
static u64 pch_systime_read(struct pch_ts_regs __iomem *regs)
{
u64 ns;
- u32 lo, hi;
- lo = ioread32(&regs->systime_lo);
- hi = ioread32(&regs->systime_hi);
+ ns = ioread64_lo_hi(&regs->systime_lo);
- ns = ((u64) hi) << 32;
- ns |= lo;
- ns <<= TICKS_NS_SHIFT;
-
- return ns;
+ return ns << TICKS_NS_SHIFT;
}
static void pch_systime_write(struct pch_ts_regs __iomem *regs, u64 ns)
{
- u32 hi, lo;
-
- ns >>= TICKS_NS_SHIFT;
- hi = ns >> 32;
- lo = ns & 0xffffffff;
-
- iowrite32(lo, &regs->systime_lo);
- iowrite32(hi, &regs->systime_hi);
+ iowrite64_lo_hi(ns >> TICKS_NS_SHIFT, &regs->systime_lo);
}
static inline void pch_block_reset(struct pch_dev *chip)
@@ -179,17 +167,6 @@ static inline void pch_block_reset(struct pch_dev *chip)
iowrite32(val, (&chip->regs->control));
}
-u32 pch_ch_control_read(struct pci_dev *pdev)
-{
- struct pch_dev *chip = pci_get_drvdata(pdev);
- u32 val;
-
- val = ioread32(&chip->regs->ch_control);
-
- return val;
-}
-EXPORT_SYMBOL(pch_ch_control_read);
-
void pch_ch_control_write(struct pci_dev *pdev, u32 val)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
@@ -243,16 +220,10 @@ u64 pch_rx_snap_read(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
u64 ns;
- u32 lo, hi;
- lo = ioread32(&chip->regs->rx_snap_lo);
- hi = ioread32(&chip->regs->rx_snap_hi);
+ ns = ioread64_lo_hi(&chip->regs->rx_snap_lo);
- ns = ((u64) hi) << 32;
- ns |= lo;
- ns <<= TICKS_NS_SHIFT;
-
- return ns;
+ return ns << TICKS_NS_SHIFT;
}
EXPORT_SYMBOL(pch_rx_snap_read);
@@ -260,16 +231,10 @@ u64 pch_tx_snap_read(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
u64 ns;
- u32 lo, hi;
-
- lo = ioread32(&chip->regs->tx_snap_lo);
- hi = ioread32(&chip->regs->tx_snap_hi);
- ns = ((u64) hi) << 32;
- ns |= lo;
- ns <<= TICKS_NS_SHIFT;
+ ns = ioread64_lo_hi(&chip->regs->tx_snap_lo);
- return ns;
+ return ns << TICKS_NS_SHIFT;
}
EXPORT_SYMBOL(pch_tx_snap_read);
@@ -296,11 +261,13 @@ static void pch_reset(struct pch_dev *chip)
* IEEE 1588 hardware when looking at PTP
* traffic on the ethernet interface
* @addr: dress which contain the column separated address to be used.
+ * @pdev: PCI device.
*/
int pch_set_station_address(u8 *addr, struct pci_dev *pdev)
{
- s32 i;
struct pch_dev *chip = pci_get_drvdata(pdev);
+ bool valid;
+ u64 mac;
/* Verify the parameter */
if ((chip->regs == NULL) || addr == (u8 *)NULL) {
@@ -308,37 +275,15 @@ int pch_set_station_address(u8 *addr, struct pci_dev *pdev)
"invalid params returning PCH_INVALIDPARAM\n");
return PCH_INVALIDPARAM;
}
- /* For all station address bytes */
- for (i = 0; i < PCH_STATION_BYTES; i++) {
- u32 val;
- s32 tmp;
-
- tmp = hex_to_bin(addr[i * 3]);
- if (tmp < 0) {
- dev_err(&pdev->dev,
- "invalid params returning PCH_INVALIDPARAM\n");
- return PCH_INVALIDPARAM;
- }
- val = tmp * 16;
- tmp = hex_to_bin(addr[(i * 3) + 1]);
- if (tmp < 0) {
- dev_err(&pdev->dev,
- "invalid params returning PCH_INVALIDPARAM\n");
- return PCH_INVALIDPARAM;
- }
- val += tmp;
- /* Expects ':' separated addresses */
- if ((i < 5) && (addr[(i * 3) + 2] != ':')) {
- dev_err(&pdev->dev,
- "invalid params returning PCH_INVALIDPARAM\n");
- return PCH_INVALIDPARAM;
- }
- /* Ideally we should set the address only after validating
- entire string */
- dev_dbg(&pdev->dev, "invoking pch_station_set\n");
- iowrite32(val, &chip->regs->ts_st[i]);
+ valid = mac_pton(addr, (u8 *)&mac);
+ if (!valid) {
+ dev_err(&pdev->dev, "invalid params returning PCH_INVALIDPARAM\n");
+ return PCH_INVALIDPARAM;
}
+
+ dev_dbg(&pdev->dev, "invoking pch_station_set\n");
+ iowrite64_lo_hi(mac, &chip->regs->ts_st);
return 0;
}
EXPORT_SYMBOL(pch_set_station_address);
@@ -351,19 +296,16 @@ static irqreturn_t isr(int irq, void *priv)
struct pch_dev *pch_dev = priv;
struct pch_ts_regs __iomem *regs = pch_dev->regs;
struct ptp_clock_event event;
- u32 ack = 0, lo, hi, val;
+ u32 ack = 0, val;
val = ioread32(&regs->event);
if (val & PCH_TSE_SNS) {
ack |= PCH_TSE_SNS;
if (pch_dev->exts0_enabled) {
- hi = ioread32(&regs->asms_hi);
- lo = ioread32(&regs->asms_lo);
event.type = PTP_CLOCK_EXTTS;
event.index = 0;
- event.timestamp = ((u64) hi) << 32;
- event.timestamp |= lo;
+ event.timestamp = ioread64_hi_lo(&regs->asms_hi);
event.timestamp <<= TICKS_NS_SHIFT;
ptp_clock_event(pch_dev->ptp_clock, &event);
}
@@ -372,12 +314,9 @@ static irqreturn_t isr(int irq, void *priv)
if (val & PCH_TSE_SNM) {
ack |= PCH_TSE_SNM;
if (pch_dev->exts1_enabled) {
- hi = ioread32(&regs->amms_hi);
- lo = ioread32(&regs->amms_lo);
event.type = PTP_CLOCK_EXTTS;
event.index = 1;
- event.timestamp = ((u64) hi) << 32;
- event.timestamp |= lo;
+ event.timestamp = ioread64_hi_lo(&regs->asms_hi);
event.timestamp <<= TICKS_NS_SHIFT;
ptp_clock_event(pch_dev->ptp_clock, &event);
}
@@ -508,63 +447,12 @@ static const struct ptp_clock_info ptp_pch_caps = {
.enable = ptp_pch_enable,
};
-
-#ifdef CONFIG_PM
-static s32 pch_suspend(struct pci_dev *pdev, pm_message_t state)
-{
- pci_disable_device(pdev);
- pci_enable_wake(pdev, PCI_D3hot, 0);
-
- if (pci_save_state(pdev) != 0) {
- dev_err(&pdev->dev, "could not save PCI config state\n");
- return -ENOMEM;
- }
- pci_set_power_state(pdev, pci_choose_state(pdev, state));
-
- return 0;
-}
-
-static s32 pch_resume(struct pci_dev *pdev)
-{
- s32 ret;
-
- pci_set_power_state(pdev, PCI_D0);
- pci_restore_state(pdev);
- ret = pci_enable_device(pdev);
- if (ret) {
- dev_err(&pdev->dev, "pci_enable_device failed\n");
- return ret;
- }
- pci_enable_wake(pdev, PCI_D3hot, 0);
- return 0;
-}
-#else
-#define pch_suspend NULL
-#define pch_resume NULL
-#endif
-
static void pch_remove(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
+ free_irq(pdev->irq, chip);
ptp_clock_unregister(chip->ptp_clock);
- /* free the interrupt */
- if (pdev->irq != 0)
- free_irq(pdev->irq, chip);
-
- /* unmap the virtual IO memory space */
- if (chip->regs != NULL) {
- iounmap(chip->regs);
- chip->regs = NULL;
- }
- /* release the reserved IO memory space */
- if (chip->mem_base != 0) {
- release_mem_region(chip->mem_base, chip->mem_size);
- chip->mem_base = 0;
- }
- pci_disable_device(pdev);
- kfree(chip);
- dev_info(&pdev->dev, "complete\n");
}
static s32
@@ -574,50 +462,29 @@ pch_probe(struct pci_dev *pdev, const struct pci_device_id *id)
unsigned long flags;
struct pch_dev *chip;
- chip = kzalloc(sizeof(struct pch_dev), GFP_KERNEL);
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (chip == NULL)
return -ENOMEM;
/* enable the 1588 pci device */
- ret = pci_enable_device(pdev);
+ ret = pcim_enable_device(pdev);
if (ret != 0) {
dev_err(&pdev->dev, "could not enable the pci device\n");
- goto err_pci_en;
+ return ret;
}
- chip->mem_base = pci_resource_start(pdev, IO_MEM_BAR);
- if (!chip->mem_base) {
+ ret = pcim_iomap_regions(pdev, BIT(IO_MEM_BAR), "1588_regs");
+ if (ret) {
dev_err(&pdev->dev, "could not locate IO memory address\n");
- ret = -ENODEV;
- goto err_pci_start;
- }
-
- /* retrieve the available length of the IO memory space */
- chip->mem_size = pci_resource_len(pdev, IO_MEM_BAR);
-
- /* allocate the memory for the device registers */
- if (!request_mem_region(chip->mem_base, chip->mem_size, "1588_regs")) {
- dev_err(&pdev->dev,
- "could not allocate register memory space\n");
- ret = -EBUSY;
- goto err_req_mem_region;
+ return ret;
}
/* get the virtual address to the 1588 registers */
- chip->regs = ioremap(chip->mem_base, chip->mem_size);
-
- if (!chip->regs) {
- dev_err(&pdev->dev, "Could not get virtual address\n");
- ret = -ENOMEM;
- goto err_ioremap;
- }
-
+ chip->regs = pcim_iomap_table(pdev)[IO_MEM_BAR];
chip->caps = ptp_pch_caps;
chip->ptp_clock = ptp_clock_register(&chip->caps, &pdev->dev);
- if (IS_ERR(chip->ptp_clock)) {
- ret = PTR_ERR(chip->ptp_clock);
- goto err_ptp_clock_reg;
- }
+ if (IS_ERR(chip->ptp_clock))
+ return PTR_ERR(chip->ptp_clock);
spin_lock_init(&chip->register_lock);
@@ -637,8 +504,7 @@ pch_probe(struct pci_dev *pdev, const struct pci_device_id *id)
pch_reset(chip);
iowrite32(DEFAULT_ADDEND, &chip->regs->addend);
- iowrite32(1, &chip->regs->trgt_lo);
- iowrite32(0, &chip->regs->trgt_hi);
+ iowrite64_lo_hi(1, &chip->regs->trgt_lo);
iowrite32(PCH_TSE_TTIPEND, &chip->regs->event);
pch_eth_enable_set(chip);
@@ -656,21 +522,7 @@ pch_probe(struct pci_dev *pdev, const struct pci_device_id *id)
err_req_irq:
ptp_clock_unregister(chip->ptp_clock);
-err_ptp_clock_reg:
- iounmap(chip->regs);
- chip->regs = NULL;
-
-err_ioremap:
- release_mem_region(chip->mem_base, chip->mem_size);
-err_req_mem_region:
- chip->mem_base = 0;
-
-err_pci_start:
- pci_disable_device(pdev);
-
-err_pci_en:
- kfree(chip);
dev_err(&pdev->dev, "probe failed(ret=0x%x)\n", ret);
return ret;
@@ -683,33 +535,15 @@ static const struct pci_device_id pch_ieee1588_pcidev_id[] = {
},
{0}
};
+MODULE_DEVICE_TABLE(pci, pch_ieee1588_pcidev_id);
static struct pci_driver pch_driver = {
.name = KBUILD_MODNAME,
.id_table = pch_ieee1588_pcidev_id,
.probe = pch_probe,
.remove = pch_remove,
- .suspend = pch_suspend,
- .resume = pch_resume,
};
-
-static void __exit ptp_pch_exit(void)
-{
- pci_unregister_driver(&pch_driver);
-}
-
-static s32 __init ptp_pch_init(void)
-{
- s32 ret;
-
- /* register the driver with the pci core */
- ret = pci_register_driver(&pch_driver);
-
- return ret;
-}
-
-module_init(ptp_pch_init);
-module_exit(ptp_pch_exit);
+module_pci_driver(pch_driver);
module_param_string(station,
pch_param.station, sizeof(pch_param.station), 0444);
diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h
index 6b97155148f1..77918a2c6701 100644
--- a/drivers/ptp/ptp_private.h
+++ b/drivers/ptp/ptp_private.h
@@ -18,6 +18,7 @@
#define PTP_MAX_TIMESTAMPS 128
#define PTP_BUF_TIMESTAMPS 30
+#define PTP_DEFAULT_MAX_VCLOCKS 20
struct timestamp_event_queue {
struct ptp_extts_event buf[PTP_MAX_TIMESTAMPS];
@@ -46,6 +47,26 @@ struct ptp_clock {
const struct attribute_group *pin_attr_groups[2];
struct kthread_worker *kworker;
struct kthread_delayed_work aux_work;
+ unsigned int max_vclocks;
+ unsigned int n_vclocks;
+ int *vclock_index;
+ struct mutex n_vclocks_mux; /* protect concurrent n_vclocks access */
+ bool is_virtual_clock;
+ bool has_cycles;
+};
+
+#define info_to_vclock(d) container_of((d), struct ptp_vclock, info)
+#define cc_to_vclock(d) container_of((d), struct ptp_vclock, cc)
+#define dw_to_vclock(d) container_of((d), struct ptp_vclock, refresh_work)
+
+struct ptp_vclock {
+ struct ptp_clock *pclock;
+ struct ptp_clock_info info;
+ struct ptp_clock *clock;
+ struct hlist_node vclock_hash_node;
+ struct cyclecounter cc;
+ struct timecounter tc;
+ spinlock_t lock; /* protects tc/cc */
};
/*
@@ -61,6 +82,33 @@ static inline int queue_cnt(struct timestamp_event_queue *q)
return cnt < 0 ? PTP_MAX_TIMESTAMPS + cnt : cnt;
}
+/* Check if ptp virtual clock is in use */
+static inline bool ptp_vclock_in_use(struct ptp_clock *ptp)
+{
+ bool in_use = false;
+
+ if (mutex_lock_interruptible(&ptp->n_vclocks_mux))
+ return true;
+
+ if (!ptp->is_virtual_clock && ptp->n_vclocks)
+ in_use = true;
+
+ mutex_unlock(&ptp->n_vclocks_mux);
+
+ return in_use;
+}
+
+/* Check if ptp clock shall be free running */
+static inline bool ptp_clock_freerun(struct ptp_clock *ptp)
+{
+ if (ptp->has_cycles)
+ return false;
+
+ return ptp_vclock_in_use(ptp);
+}
+
+extern struct class *ptp_class;
+
/*
* see ptp_chardev.c
*/
@@ -89,4 +137,6 @@ extern const struct attribute_group *ptp_groups[];
int ptp_populate_pin_groups(struct ptp_clock *ptp);
void ptp_cleanup_pin_groups(struct ptp_clock *ptp);
+struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock);
+void ptp_vclock_unregister(struct ptp_vclock *vclock);
#endif
diff --git a/drivers/ptp/ptp_qoriq.c b/drivers/ptp/ptp_qoriq.c
index b27c46ebfc8f..08f4cf0ad9e3 100644
--- a/drivers/ptp/ptp_qoriq.c
+++ b/drivers/ptp/ptp_qoriq.c
@@ -72,6 +72,10 @@ static void set_fipers(struct ptp_qoriq *ptp_qoriq)
set_alarm(ptp_qoriq);
ptp_qoriq->write(&regs->fiper_regs->tmr_fiper1, ptp_qoriq->tmr_fiper1);
ptp_qoriq->write(&regs->fiper_regs->tmr_fiper2, ptp_qoriq->tmr_fiper2);
+
+ if (ptp_qoriq->fiper3_support)
+ ptp_qoriq->write(&regs->fiper_regs->tmr_fiper3,
+ ptp_qoriq->tmr_fiper3);
}
int extts_clean_up(struct ptp_qoriq *ptp_qoriq, int index, bool update_event)
@@ -131,8 +135,7 @@ irqreturn_t ptp_qoriq_isr(int irq, void *priv)
struct ptp_qoriq *ptp_qoriq = priv;
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
struct ptp_clock_event event;
- u64 ns;
- u32 ack = 0, lo, hi, mask, val, irqs;
+ u32 ack = 0, mask, val, irqs;
spin_lock(&ptp_qoriq->lock);
@@ -153,32 +156,6 @@ irqreturn_t ptp_qoriq_isr(int irq, void *priv)
extts_clean_up(ptp_qoriq, 1, true);
}
- if (irqs & ALM2) {
- ack |= ALM2;
- if (ptp_qoriq->alarm_value) {
- event.type = PTP_CLOCK_ALARM;
- event.index = 0;
- event.timestamp = ptp_qoriq->alarm_value;
- ptp_clock_event(ptp_qoriq->clock, &event);
- }
- if (ptp_qoriq->alarm_interval) {
- ns = ptp_qoriq->alarm_value + ptp_qoriq->alarm_interval;
- hi = ns >> 32;
- lo = ns & 0xffffffff;
- ptp_qoriq->write(&regs->alarm_regs->tmr_alarm2_l, lo);
- ptp_qoriq->write(&regs->alarm_regs->tmr_alarm2_h, hi);
- ptp_qoriq->alarm_value = ns;
- } else {
- spin_lock(&ptp_qoriq->lock);
- mask = ptp_qoriq->read(&regs->ctrl_regs->tmr_temask);
- mask &= ~ALM2EN;
- ptp_qoriq->write(&regs->ctrl_regs->tmr_temask, mask);
- spin_unlock(&ptp_qoriq->lock);
- ptp_qoriq->alarm_value = 0;
- ptp_qoriq->alarm_interval = 0;
- }
- }
-
if (irqs & PP1) {
ack |= PP1;
event.type = PTP_CLOCK_PPS;
@@ -212,15 +189,16 @@ int ptp_qoriq_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
tmr_add = ptp_qoriq->tmr_add;
adj = tmr_add;
- /* calculate diff as adj*(scaled_ppm/65536)/1000000
- * and round() to the nearest integer
+ /*
+ * Calculate diff and round() to the nearest integer
+ *
+ * diff = adj * (ppb / 1000000000)
+ * = adj * scaled_ppm / 65536000000
*/
- adj *= scaled_ppm;
- diff = div_u64(adj, 8000000);
- diff = (diff >> 13) + ((diff >> 12) & 1);
+ diff = mul_u64_u64_div_u64(adj, scaled_ppm, 32768000000);
+ diff = DIV64_U64_ROUND_UP(diff, 2);
tmr_add = neg_adj ? tmr_add - diff : tmr_add + diff;
-
ptp_qoriq->write(&regs->ctrl_regs->tmr_add, tmr_add);
return 0;
@@ -393,6 +371,7 @@ static u32 ptp_qoriq_nominal_freq(u32 clk_src)
* "fsl,tmr-add"
* "fsl,tmr-fiper1"
* "fsl,tmr-fiper2"
+ * "fsl,tmr-fiper3" (required only for DPAA2 and ENETC hardware)
* "fsl,max-adj"
*
* Return 0 if success
@@ -439,6 +418,7 @@ static int ptp_qoriq_auto_config(struct ptp_qoriq *ptp_qoriq,
ptp_qoriq->tmr_add = freq_comp;
ptp_qoriq->tmr_fiper1 = DEFAULT_FIPER1_PERIOD - ptp_qoriq->tclk_period;
ptp_qoriq->tmr_fiper2 = DEFAULT_FIPER2_PERIOD - ptp_qoriq->tclk_period;
+ ptp_qoriq->tmr_fiper3 = DEFAULT_FIPER3_PERIOD - ptp_qoriq->tclk_period;
/* max_adj = 1000000000 * (freq_ratio - 1.0) - 1
* freq_ratio = reference_clock_freq / nominal_freq
@@ -473,6 +453,10 @@ int ptp_qoriq_init(struct ptp_qoriq *ptp_qoriq, void __iomem *base,
else
ptp_qoriq->extts_fifo_support = false;
+ if (of_device_is_compatible(node, "fsl,dpaa2-ptp") ||
+ of_device_is_compatible(node, "fsl,enetc-ptp"))
+ ptp_qoriq->fiper3_support = true;
+
if (of_property_read_u32(node,
"fsl,tclk-period", &ptp_qoriq->tclk_period) ||
of_property_read_u32(node,
@@ -484,7 +468,10 @@ int ptp_qoriq_init(struct ptp_qoriq *ptp_qoriq, void __iomem *base,
of_property_read_u32(node,
"fsl,tmr-fiper2", &ptp_qoriq->tmr_fiper2) ||
of_property_read_u32(node,
- "fsl,max-adj", &ptp_qoriq->caps.max_adj)) {
+ "fsl,max-adj", &ptp_qoriq->caps.max_adj) ||
+ (ptp_qoriq->fiper3_support &&
+ of_property_read_u32(node, "fsl,tmr-fiper3",
+ &ptp_qoriq->tmr_fiper3))) {
pr_warn("device tree node missing required elements, try automatic configuration\n");
if (ptp_qoriq_auto_config(ptp_qoriq, node))
@@ -529,6 +516,11 @@ int ptp_qoriq_init(struct ptp_qoriq *ptp_qoriq, void __iomem *base,
ptp_qoriq->write(&regs->ctrl_regs->tmr_prsc, ptp_qoriq->tmr_prsc);
ptp_qoriq->write(&regs->fiper_regs->tmr_fiper1, ptp_qoriq->tmr_fiper1);
ptp_qoriq->write(&regs->fiper_regs->tmr_fiper2, ptp_qoriq->tmr_fiper2);
+
+ if (ptp_qoriq->fiper3_support)
+ ptp_qoriq->write(&regs->fiper_regs->tmr_fiper3,
+ ptp_qoriq->tmr_fiper3);
+
set_alarm(ptp_qoriq);
ptp_qoriq->write(&regs->ctrl_regs->tmr_ctrl,
tmr_ctrl|FIPERST|RTPE|TE|FRD);
diff --git a/drivers/ptp/ptp_sysfs.c b/drivers/ptp/ptp_sysfs.c
index be076a91e20e..f30b0a439470 100644
--- a/drivers/ptp/ptp_sysfs.c
+++ b/drivers/ptp/ptp_sysfs.c
@@ -3,6 +3,7 @@
* PTP 1588 clock support - sysfs interface.
*
* Copyright (C) 2010 OMICRON electronics GmbH
+ * Copyright 2021 NXP
*/
#include <linux/capability.h>
#include <linux/slab.h>
@@ -13,7 +14,7 @@ static ssize_t clock_name_show(struct device *dev,
struct device_attribute *attr, char *page)
{
struct ptp_clock *ptp = dev_get_drvdata(dev);
- return snprintf(page, PAGE_SIZE-1, "%s\n", ptp->info->name);
+ return sysfs_emit(page, "%s\n", ptp->info->name);
}
static DEVICE_ATTR_RO(clock_name);
@@ -148,6 +149,162 @@ out:
}
static DEVICE_ATTR(pps_enable, 0220, NULL, pps_enable_store);
+static int unregister_vclock(struct device *dev, void *data)
+{
+ struct ptp_clock *ptp = dev_get_drvdata(dev);
+ struct ptp_clock_info *info = ptp->info;
+ struct ptp_vclock *vclock;
+ u32 *num = data;
+
+ vclock = info_to_vclock(info);
+ dev_info(dev->parent, "delete virtual clock ptp%d\n",
+ vclock->clock->index);
+
+ ptp_vclock_unregister(vclock);
+ (*num)--;
+
+ /* For break. Not error. */
+ if (*num == 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static ssize_t n_vclocks_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ struct ptp_clock *ptp = dev_get_drvdata(dev);
+ ssize_t size;
+
+ if (mutex_lock_interruptible(&ptp->n_vclocks_mux))
+ return -ERESTARTSYS;
+
+ size = snprintf(page, PAGE_SIZE - 1, "%u\n", ptp->n_vclocks);
+
+ mutex_unlock(&ptp->n_vclocks_mux);
+
+ return size;
+}
+
+static ssize_t n_vclocks_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_clock *ptp = dev_get_drvdata(dev);
+ struct ptp_vclock *vclock;
+ int err = -EINVAL;
+ u32 num, i;
+
+ if (kstrtou32(buf, 0, &num))
+ return err;
+
+ if (mutex_lock_interruptible(&ptp->n_vclocks_mux))
+ return -ERESTARTSYS;
+
+ if (num > ptp->max_vclocks) {
+ dev_err(dev, "max value is %d\n", ptp->max_vclocks);
+ goto out;
+ }
+
+ /* Need to create more vclocks */
+ if (num > ptp->n_vclocks) {
+ for (i = 0; i < num - ptp->n_vclocks; i++) {
+ vclock = ptp_vclock_register(ptp);
+ if (!vclock)
+ goto out;
+
+ *(ptp->vclock_index + ptp->n_vclocks + i) =
+ vclock->clock->index;
+
+ dev_info(dev, "new virtual clock ptp%d\n",
+ vclock->clock->index);
+ }
+ }
+
+ /* Need to delete vclocks */
+ if (num < ptp->n_vclocks) {
+ i = ptp->n_vclocks - num;
+ device_for_each_child_reverse(dev, &i,
+ unregister_vclock);
+
+ for (i = 1; i <= ptp->n_vclocks - num; i++)
+ *(ptp->vclock_index + ptp->n_vclocks - i) = -1;
+ }
+
+ /* Need to inform about changed physical clock behavior */
+ if (!ptp->has_cycles) {
+ if (num == 0)
+ dev_info(dev, "only physical clock in use now\n");
+ else
+ dev_info(dev, "guarantee physical clock free running\n");
+ }
+
+ ptp->n_vclocks = num;
+ mutex_unlock(&ptp->n_vclocks_mux);
+
+ return count;
+out:
+ mutex_unlock(&ptp->n_vclocks_mux);
+ return err;
+}
+static DEVICE_ATTR_RW(n_vclocks);
+
+static ssize_t max_vclocks_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ struct ptp_clock *ptp = dev_get_drvdata(dev);
+ ssize_t size;
+
+ size = snprintf(page, PAGE_SIZE - 1, "%u\n", ptp->max_vclocks);
+
+ return size;
+}
+
+static ssize_t max_vclocks_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_clock *ptp = dev_get_drvdata(dev);
+ unsigned int *vclock_index;
+ int err = -EINVAL;
+ size_t size;
+ u32 max;
+
+ if (kstrtou32(buf, 0, &max) || max == 0)
+ return -EINVAL;
+
+ if (max == ptp->max_vclocks)
+ return count;
+
+ if (mutex_lock_interruptible(&ptp->n_vclocks_mux))
+ return -ERESTARTSYS;
+
+ if (max < ptp->n_vclocks)
+ goto out;
+
+ size = sizeof(int) * max;
+ vclock_index = kzalloc(size, GFP_KERNEL);
+ if (!vclock_index) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ size = sizeof(int) * ptp->n_vclocks;
+ memcpy(vclock_index, ptp->vclock_index, size);
+
+ kfree(ptp->vclock_index);
+ ptp->vclock_index = vclock_index;
+ ptp->max_vclocks = max;
+
+ mutex_unlock(&ptp->n_vclocks_mux);
+
+ return count;
+out:
+ mutex_unlock(&ptp->n_vclocks_mux);
+ return err;
+}
+static DEVICE_ATTR_RW(max_vclocks);
+
static struct attribute *ptp_attrs[] = {
&dev_attr_clock_name.attr,
@@ -162,6 +319,8 @@ static struct attribute *ptp_attrs[] = {
&dev_attr_fifo.attr,
&dev_attr_period.attr,
&dev_attr_pps_enable.attr,
+ &dev_attr_n_vclocks.attr,
+ &dev_attr_max_vclocks.attr,
NULL
};
@@ -183,6 +342,10 @@ static umode_t ptp_is_attribute_visible(struct kobject *kobj,
} else if (attr == &dev_attr_pps_enable.attr) {
if (!info->pps)
mode = 0;
+ } else if (attr == &dev_attr_n_vclocks.attr ||
+ attr == &dev_attr_max_vclocks.attr) {
+ if (ptp->is_virtual_clock)
+ mode = 0;
}
return mode;
@@ -227,7 +390,7 @@ static ssize_t ptp_pin_show(struct device *dev, struct device_attribute *attr,
mutex_unlock(&ptp->pincfg_mux);
- return snprintf(page, PAGE_SIZE, "%u %u\n", func, chan);
+ return sysfs_emit(page, "%u %u\n", func, chan);
}
static ssize_t ptp_pin_store(struct device *dev, struct device_attribute *attr,
diff --git a/drivers/ptp/ptp_vclock.c b/drivers/ptp/ptp_vclock.c
new file mode 100644
index 000000000000..1c0ed4805c0a
--- /dev/null
+++ b/drivers/ptp/ptp_vclock.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PTP virtual clock driver
+ *
+ * Copyright 2021 NXP
+ */
+#include <linux/slab.h>
+#include <linux/hashtable.h>
+#include "ptp_private.h"
+
+#define PTP_VCLOCK_CC_SHIFT 31
+#define PTP_VCLOCK_CC_MULT (1 << PTP_VCLOCK_CC_SHIFT)
+#define PTP_VCLOCK_FADJ_SHIFT 9
+#define PTP_VCLOCK_FADJ_DENOMINATOR 15625ULL
+#define PTP_VCLOCK_REFRESH_INTERVAL (HZ * 2)
+
+/* protects vclock_hash addition/deletion */
+static DEFINE_SPINLOCK(vclock_hash_lock);
+
+static DEFINE_READ_MOSTLY_HASHTABLE(vclock_hash, 8);
+
+static void ptp_vclock_hash_add(struct ptp_vclock *vclock)
+{
+ spin_lock(&vclock_hash_lock);
+
+ hlist_add_head_rcu(&vclock->vclock_hash_node,
+ &vclock_hash[vclock->clock->index % HASH_SIZE(vclock_hash)]);
+
+ spin_unlock(&vclock_hash_lock);
+}
+
+static void ptp_vclock_hash_del(struct ptp_vclock *vclock)
+{
+ spin_lock(&vclock_hash_lock);
+
+ hlist_del_init_rcu(&vclock->vclock_hash_node);
+
+ spin_unlock(&vclock_hash_lock);
+
+ synchronize_rcu();
+}
+
+static int ptp_vclock_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct ptp_vclock *vclock = info_to_vclock(ptp);
+ unsigned long flags;
+ s64 adj;
+
+ adj = (s64)scaled_ppm << PTP_VCLOCK_FADJ_SHIFT;
+ adj = div_s64(adj, PTP_VCLOCK_FADJ_DENOMINATOR);
+
+ spin_lock_irqsave(&vclock->lock, flags);
+ timecounter_read(&vclock->tc);
+ vclock->cc.mult = PTP_VCLOCK_CC_MULT + adj;
+ spin_unlock_irqrestore(&vclock->lock, flags);
+
+ return 0;
+}
+
+static int ptp_vclock_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct ptp_vclock *vclock = info_to_vclock(ptp);
+ unsigned long flags;
+
+ spin_lock_irqsave(&vclock->lock, flags);
+ timecounter_adjtime(&vclock->tc, delta);
+ spin_unlock_irqrestore(&vclock->lock, flags);
+
+ return 0;
+}
+
+static int ptp_vclock_gettime(struct ptp_clock_info *ptp,
+ struct timespec64 *ts)
+{
+ struct ptp_vclock *vclock = info_to_vclock(ptp);
+ unsigned long flags;
+ u64 ns;
+
+ spin_lock_irqsave(&vclock->lock, flags);
+ ns = timecounter_read(&vclock->tc);
+ spin_unlock_irqrestore(&vclock->lock, flags);
+ *ts = ns_to_timespec64(ns);
+
+ return 0;
+}
+
+static int ptp_vclock_gettimex(struct ptp_clock_info *ptp,
+ struct timespec64 *ts,
+ struct ptp_system_timestamp *sts)
+{
+ struct ptp_vclock *vclock = info_to_vclock(ptp);
+ struct ptp_clock *pptp = vclock->pclock;
+ struct timespec64 pts;
+ unsigned long flags;
+ int err;
+ u64 ns;
+
+ err = pptp->info->getcyclesx64(pptp->info, &pts, sts);
+ if (err)
+ return err;
+
+ spin_lock_irqsave(&vclock->lock, flags);
+ ns = timecounter_cyc2time(&vclock->tc, timespec64_to_ns(&pts));
+ spin_unlock_irqrestore(&vclock->lock, flags);
+
+ *ts = ns_to_timespec64(ns);
+
+ return 0;
+}
+
+static int ptp_vclock_settime(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct ptp_vclock *vclock = info_to_vclock(ptp);
+ u64 ns = timespec64_to_ns(ts);
+ unsigned long flags;
+
+ spin_lock_irqsave(&vclock->lock, flags);
+ timecounter_init(&vclock->tc, &vclock->cc, ns);
+ spin_unlock_irqrestore(&vclock->lock, flags);
+
+ return 0;
+}
+
+static int ptp_vclock_getcrosststamp(struct ptp_clock_info *ptp,
+ struct system_device_crosststamp *xtstamp)
+{
+ struct ptp_vclock *vclock = info_to_vclock(ptp);
+ struct ptp_clock *pptp = vclock->pclock;
+ unsigned long flags;
+ int err;
+ u64 ns;
+
+ err = pptp->info->getcrosscycles(pptp->info, xtstamp);
+ if (err)
+ return err;
+
+ spin_lock_irqsave(&vclock->lock, flags);
+ ns = timecounter_cyc2time(&vclock->tc, ktime_to_ns(xtstamp->device));
+ spin_unlock_irqrestore(&vclock->lock, flags);
+
+ xtstamp->device = ns_to_ktime(ns);
+
+ return 0;
+}
+
+static long ptp_vclock_refresh(struct ptp_clock_info *ptp)
+{
+ struct ptp_vclock *vclock = info_to_vclock(ptp);
+ struct timespec64 ts;
+
+ ptp_vclock_gettime(&vclock->info, &ts);
+
+ return PTP_VCLOCK_REFRESH_INTERVAL;
+}
+
+static const struct ptp_clock_info ptp_vclock_info = {
+ .owner = THIS_MODULE,
+ .name = "ptp virtual clock",
+ .max_adj = 500000000,
+ .adjfine = ptp_vclock_adjfine,
+ .adjtime = ptp_vclock_adjtime,
+ .settime64 = ptp_vclock_settime,
+ .do_aux_work = ptp_vclock_refresh,
+};
+
+static u64 ptp_vclock_read(const struct cyclecounter *cc)
+{
+ struct ptp_vclock *vclock = cc_to_vclock(cc);
+ struct ptp_clock *ptp = vclock->pclock;
+ struct timespec64 ts = {};
+
+ ptp->info->getcycles64(ptp->info, &ts);
+
+ return timespec64_to_ns(&ts);
+}
+
+static const struct cyclecounter ptp_vclock_cc = {
+ .read = ptp_vclock_read,
+ .mask = CYCLECOUNTER_MASK(32),
+ .mult = PTP_VCLOCK_CC_MULT,
+ .shift = PTP_VCLOCK_CC_SHIFT,
+};
+
+struct ptp_vclock *ptp_vclock_register(struct ptp_clock *pclock)
+{
+ struct ptp_vclock *vclock;
+
+ vclock = kzalloc(sizeof(*vclock), GFP_KERNEL);
+ if (!vclock)
+ return NULL;
+
+ vclock->pclock = pclock;
+ vclock->info = ptp_vclock_info;
+ if (pclock->info->getcyclesx64)
+ vclock->info.gettimex64 = ptp_vclock_gettimex;
+ else
+ vclock->info.gettime64 = ptp_vclock_gettime;
+ if (pclock->info->getcrosscycles)
+ vclock->info.getcrosststamp = ptp_vclock_getcrosststamp;
+ vclock->cc = ptp_vclock_cc;
+
+ snprintf(vclock->info.name, PTP_CLOCK_NAME_LEN, "ptp%d_virt",
+ pclock->index);
+
+ INIT_HLIST_NODE(&vclock->vclock_hash_node);
+
+ spin_lock_init(&vclock->lock);
+
+ vclock->clock = ptp_clock_register(&vclock->info, &pclock->dev);
+ if (IS_ERR_OR_NULL(vclock->clock)) {
+ kfree(vclock);
+ return NULL;
+ }
+
+ timecounter_init(&vclock->tc, &vclock->cc, 0);
+ ptp_schedule_worker(vclock->clock, PTP_VCLOCK_REFRESH_INTERVAL);
+
+ ptp_vclock_hash_add(vclock);
+
+ return vclock;
+}
+
+void ptp_vclock_unregister(struct ptp_vclock *vclock)
+{
+ ptp_vclock_hash_del(vclock);
+
+ ptp_clock_unregister(vclock->clock);
+ kfree(vclock);
+}
+
+#if IS_BUILTIN(CONFIG_PTP_1588_CLOCK)
+int ptp_get_vclocks_index(int pclock_index, int **vclock_index)
+{
+ char name[PTP_CLOCK_NAME_LEN] = "";
+ struct ptp_clock *ptp;
+ struct device *dev;
+ int num = 0;
+
+ if (pclock_index < 0)
+ return num;
+
+ snprintf(name, PTP_CLOCK_NAME_LEN, "ptp%d", pclock_index);
+ dev = class_find_device_by_name(ptp_class, name);
+ if (!dev)
+ return num;
+
+ ptp = dev_get_drvdata(dev);
+
+ if (mutex_lock_interruptible(&ptp->n_vclocks_mux)) {
+ put_device(dev);
+ return num;
+ }
+
+ *vclock_index = kzalloc(sizeof(int) * ptp->n_vclocks, GFP_KERNEL);
+ if (!(*vclock_index))
+ goto out;
+
+ memcpy(*vclock_index, ptp->vclock_index, sizeof(int) * ptp->n_vclocks);
+ num = ptp->n_vclocks;
+out:
+ mutex_unlock(&ptp->n_vclocks_mux);
+ put_device(dev);
+ return num;
+}
+EXPORT_SYMBOL(ptp_get_vclocks_index);
+
+ktime_t ptp_convert_timestamp(const ktime_t *hwtstamp, int vclock_index)
+{
+ unsigned int hash = vclock_index % HASH_SIZE(vclock_hash);
+ struct ptp_vclock *vclock;
+ unsigned long flags;
+ u64 ns;
+ u64 vclock_ns = 0;
+
+ ns = ktime_to_ns(*hwtstamp);
+
+ rcu_read_lock();
+
+ hlist_for_each_entry_rcu(vclock, &vclock_hash[hash], vclock_hash_node) {
+ if (vclock->clock->index != vclock_index)
+ continue;
+
+ spin_lock_irqsave(&vclock->lock, flags);
+ vclock_ns = timecounter_cyc2time(&vclock->tc, ns);
+ spin_unlock_irqrestore(&vclock->lock, flags);
+ break;
+ }
+
+ rcu_read_unlock();
+
+ return ns_to_ktime(vclock_ns);
+}
+EXPORT_SYMBOL(ptp_convert_timestamp);
+#endif
diff --git a/drivers/ptp/ptp_vmw.c b/drivers/ptp/ptp_vmw.c
new file mode 100644
index 000000000000..5dca26e14bdc
--- /dev/null
+++ b/drivers/ptp/ptp_vmw.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Copyright (C) 2020 VMware, Inc., Palo Alto, CA., USA
+ *
+ * PTP clock driver for VMware precision clock virtual device.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/ptp_clock_kernel.h>
+#include <asm/hypervisor.h>
+#include <asm/vmware.h>
+
+#define VMWARE_MAGIC 0x564D5868
+#define VMWARE_CMD_PCLK(nr) ((nr << 16) | 97)
+#define VMWARE_CMD_PCLK_GETTIME VMWARE_CMD_PCLK(0)
+
+static struct acpi_device *ptp_vmw_acpi_device;
+static struct ptp_clock *ptp_vmw_clock;
+
+
+static int ptp_vmw_pclk_read(u64 *ns)
+{
+ u32 ret, nsec_hi, nsec_lo, unused1, unused2, unused3;
+
+ asm volatile (VMWARE_HYPERCALL :
+ "=a"(ret), "=b"(nsec_hi), "=c"(nsec_lo), "=d"(unused1),
+ "=S"(unused2), "=D"(unused3) :
+ "a"(VMWARE_MAGIC), "b"(0),
+ "c"(VMWARE_CMD_PCLK_GETTIME), "d"(0) :
+ "memory");
+
+ if (ret == 0)
+ *ns = ((u64)nsec_hi << 32) | nsec_lo;
+ return ret;
+}
+
+/*
+ * PTP clock ops.
+ */
+
+static int ptp_vmw_adjtime(struct ptp_clock_info *info, s64 delta)
+{
+ return -EOPNOTSUPP;
+}
+
+static int ptp_vmw_adjfreq(struct ptp_clock_info *info, s32 delta)
+{
+ return -EOPNOTSUPP;
+}
+
+static int ptp_vmw_gettime(struct ptp_clock_info *info, struct timespec64 *ts)
+{
+ u64 ns;
+
+ if (ptp_vmw_pclk_read(&ns) != 0)
+ return -EIO;
+ *ts = ns_to_timespec64(ns);
+ return 0;
+}
+
+static int ptp_vmw_settime(struct ptp_clock_info *info,
+ const struct timespec64 *ts)
+{
+ return -EOPNOTSUPP;
+}
+
+static int ptp_vmw_enable(struct ptp_clock_info *info,
+ struct ptp_clock_request *request, int on)
+{
+ return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info ptp_vmw_clock_info = {
+ .owner = THIS_MODULE,
+ .name = "ptp_vmw",
+ .max_adj = 0,
+ .adjtime = ptp_vmw_adjtime,
+ .adjfreq = ptp_vmw_adjfreq,
+ .gettime64 = ptp_vmw_gettime,
+ .settime64 = ptp_vmw_settime,
+ .enable = ptp_vmw_enable,
+};
+
+/*
+ * ACPI driver ops for VMware "precision clock" virtual device.
+ */
+
+static int ptp_vmw_acpi_add(struct acpi_device *device)
+{
+ ptp_vmw_clock = ptp_clock_register(&ptp_vmw_clock_info, NULL);
+ if (IS_ERR(ptp_vmw_clock)) {
+ pr_err("failed to register ptp clock\n");
+ return PTR_ERR(ptp_vmw_clock);
+ }
+
+ ptp_vmw_acpi_device = device;
+ return 0;
+}
+
+static int ptp_vmw_acpi_remove(struct acpi_device *device)
+{
+ ptp_clock_unregister(ptp_vmw_clock);
+ return 0;
+}
+
+static const struct acpi_device_id ptp_vmw_acpi_device_ids[] = {
+ { "VMW0005", 0 },
+ { "", 0 },
+};
+
+MODULE_DEVICE_TABLE(acpi, ptp_vmw_acpi_device_ids);
+
+static struct acpi_driver ptp_vmw_acpi_driver = {
+ .name = "ptp_vmw",
+ .ids = ptp_vmw_acpi_device_ids,
+ .ops = {
+ .add = ptp_vmw_acpi_add,
+ .remove = ptp_vmw_acpi_remove
+ },
+ .owner = THIS_MODULE
+};
+
+static int __init ptp_vmw_init(void)
+{
+ if (x86_hyper_type != X86_HYPER_VMWARE)
+ return -1;
+ return acpi_bus_register_driver(&ptp_vmw_acpi_driver);
+}
+
+static void __exit ptp_vmw_exit(void)
+{
+ acpi_bus_unregister_driver(&ptp_vmw_acpi_driver);
+}
+
+module_init(ptp_vmw_init);
+module_exit(ptp_vmw_exit);
+
+MODULE_DESCRIPTION("VMware virtual PTP clock driver");
+MODULE_AUTHOR("VMware, Inc.");
+MODULE_LICENSE("Dual BSD/GPL");