1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /******************************************************************************* |
3 | Copyright (C) 2013 Vayavya Labs Pvt Ltd |
4 | |
5 | This implements all the API for managing HW timestamp & PTP. |
6 | |
7 | |
8 | Author: Rayagond Kokatanur <rayagond@vayavyalabs.com> |
9 | Author: Giuseppe Cavallaro <peppe.cavallaro@st.com> |
10 | *******************************************************************************/ |
11 | |
12 | #include <linux/io.h> |
13 | #include <linux/iopoll.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/ptp_clock_kernel.h> |
16 | #include "common.h" |
17 | #include "stmmac_ptp.h" |
18 | #include "dwmac4.h" |
19 | #include "stmmac.h" |
20 | |
21 | static void config_hw_tstamping(void __iomem *ioaddr, u32 data) |
22 | { |
23 | writel(val: data, addr: ioaddr + PTP_TCR); |
24 | } |
25 | |
26 | static void config_sub_second_increment(void __iomem *ioaddr, |
27 | u32 ptp_clock, int gmac4, u32 *ssinc) |
28 | { |
29 | u32 value = readl(addr: ioaddr + PTP_TCR); |
30 | unsigned long data; |
31 | u32 reg_value; |
32 | |
33 | /* For GMAC3.x, 4.x versions, in "fine adjustement mode" set sub-second |
34 | * increment to twice the number of nanoseconds of a clock cycle. |
35 | * The calculation of the default_addend value by the caller will set it |
36 | * to mid-range = 2^31 when the remainder of this division is zero, |
37 | * which will make the accumulator overflow once every 2 ptp_clock |
38 | * cycles, adding twice the number of nanoseconds of a clock cycle : |
39 | * 2000000000ULL / ptp_clock. |
40 | */ |
41 | if (value & PTP_TCR_TSCFUPDT) |
42 | data = (2000000000ULL / ptp_clock); |
43 | else |
44 | data = (1000000000ULL / ptp_clock); |
45 | |
46 | /* 0.465ns accuracy */ |
47 | if (!(value & PTP_TCR_TSCTRLSSR)) |
48 | data = (data * 1000) / 465; |
49 | |
50 | if (data > PTP_SSIR_SSINC_MAX) |
51 | data = PTP_SSIR_SSINC_MAX; |
52 | |
53 | reg_value = data; |
54 | if (gmac4) |
55 | reg_value <<= GMAC4_PTP_SSIR_SSINC_SHIFT; |
56 | |
57 | writel(val: reg_value, addr: ioaddr + PTP_SSIR); |
58 | |
59 | if (ssinc) |
60 | *ssinc = data; |
61 | } |
62 | |
63 | static void hwtstamp_correct_latency(struct stmmac_priv *priv) |
64 | { |
65 | void __iomem *ioaddr = priv->ptpaddr; |
66 | u32 reg_tsic, reg_tsicsns; |
67 | u32 reg_tsec, reg_tsecsns; |
68 | u64 scaled_ns; |
69 | u32 val; |
70 | |
71 | /* MAC-internal ingress latency */ |
72 | scaled_ns = readl(addr: ioaddr + PTP_TS_INGR_LAT); |
73 | |
74 | /* See section 11.7.2.5.3.1 "Ingress Correction" on page 4001 of |
75 | * i.MX8MP Applications Processor Reference Manual Rev. 1, 06/2021 |
76 | */ |
77 | val = readl(addr: ioaddr + PTP_TCR); |
78 | if (val & PTP_TCR_TSCTRLSSR) |
79 | /* nanoseconds field is in decimal format with granularity of 1ns/bit */ |
80 | scaled_ns = ((u64)NSEC_PER_SEC << 16) - scaled_ns; |
81 | else |
82 | /* nanoseconds field is in binary format with granularity of ~0.466ns/bit */ |
83 | scaled_ns = ((1ULL << 31) << 16) - |
84 | DIV_U64_ROUND_CLOSEST(scaled_ns * PSEC_PER_NSEC, 466U); |
85 | |
86 | reg_tsic = scaled_ns >> 16; |
87 | reg_tsicsns = scaled_ns & 0xff00; |
88 | |
89 | /* set bit 31 for 2's compliment */ |
90 | reg_tsic |= BIT(31); |
91 | |
92 | writel(val: reg_tsic, addr: ioaddr + PTP_TS_INGR_CORR_NS); |
93 | writel(val: reg_tsicsns, addr: ioaddr + PTP_TS_INGR_CORR_SNS); |
94 | |
95 | /* MAC-internal egress latency */ |
96 | scaled_ns = readl(addr: ioaddr + PTP_TS_EGR_LAT); |
97 | |
98 | reg_tsec = scaled_ns >> 16; |
99 | reg_tsecsns = scaled_ns & 0xff00; |
100 | |
101 | writel(val: reg_tsec, addr: ioaddr + PTP_TS_EGR_CORR_NS); |
102 | writel(val: reg_tsecsns, addr: ioaddr + PTP_TS_EGR_CORR_SNS); |
103 | } |
104 | |
105 | static int init_systime(void __iomem *ioaddr, u32 sec, u32 nsec) |
106 | { |
107 | u32 value; |
108 | |
109 | writel(val: sec, addr: ioaddr + PTP_STSUR); |
110 | writel(val: nsec, addr: ioaddr + PTP_STNSUR); |
111 | /* issue command to initialize the system time value */ |
112 | value = readl(addr: ioaddr + PTP_TCR); |
113 | value |= PTP_TCR_TSINIT; |
114 | writel(val: value, addr: ioaddr + PTP_TCR); |
115 | |
116 | /* wait for present system time initialize to complete */ |
117 | return readl_poll_timeout_atomic(ioaddr + PTP_TCR, value, |
118 | !(value & PTP_TCR_TSINIT), |
119 | 10, 100000); |
120 | } |
121 | |
122 | static int config_addend(void __iomem *ioaddr, u32 addend) |
123 | { |
124 | u32 value; |
125 | int limit; |
126 | |
127 | writel(val: addend, addr: ioaddr + PTP_TAR); |
128 | /* issue command to update the addend value */ |
129 | value = readl(addr: ioaddr + PTP_TCR); |
130 | value |= PTP_TCR_TSADDREG; |
131 | writel(val: value, addr: ioaddr + PTP_TCR); |
132 | |
133 | /* wait for present addend update to complete */ |
134 | limit = 10; |
135 | while (limit--) { |
136 | if (!(readl(addr: ioaddr + PTP_TCR) & PTP_TCR_TSADDREG)) |
137 | break; |
138 | mdelay(10); |
139 | } |
140 | if (limit < 0) |
141 | return -EBUSY; |
142 | |
143 | return 0; |
144 | } |
145 | |
146 | static int adjust_systime(void __iomem *ioaddr, u32 sec, u32 nsec, |
147 | int add_sub, int gmac4) |
148 | { |
149 | u32 value; |
150 | int limit; |
151 | |
152 | if (add_sub) { |
153 | /* If the new sec value needs to be subtracted with |
154 | * the system time, then MAC_STSUR reg should be |
155 | * programmed with (2^32 – <new_sec_value>) |
156 | */ |
157 | if (gmac4) |
158 | sec = -sec; |
159 | |
160 | value = readl(addr: ioaddr + PTP_TCR); |
161 | if (value & PTP_TCR_TSCTRLSSR) |
162 | nsec = (PTP_DIGITAL_ROLLOVER_MODE - nsec); |
163 | else |
164 | nsec = (PTP_BINARY_ROLLOVER_MODE - nsec); |
165 | } |
166 | |
167 | writel(val: sec, addr: ioaddr + PTP_STSUR); |
168 | value = (add_sub << PTP_STNSUR_ADDSUB_SHIFT) | nsec; |
169 | writel(val: value, addr: ioaddr + PTP_STNSUR); |
170 | |
171 | /* issue command to initialize the system time value */ |
172 | value = readl(addr: ioaddr + PTP_TCR); |
173 | value |= PTP_TCR_TSUPDT; |
174 | writel(val: value, addr: ioaddr + PTP_TCR); |
175 | |
176 | /* wait for present system time adjust/update to complete */ |
177 | limit = 10; |
178 | while (limit--) { |
179 | if (!(readl(addr: ioaddr + PTP_TCR) & PTP_TCR_TSUPDT)) |
180 | break; |
181 | mdelay(10); |
182 | } |
183 | if (limit < 0) |
184 | return -EBUSY; |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static void get_systime(void __iomem *ioaddr, u64 *systime) |
190 | { |
191 | u64 ns, sec0, sec1; |
192 | |
193 | /* Get the TSS value */ |
194 | sec1 = readl_relaxed(ioaddr + PTP_STSR); |
195 | do { |
196 | sec0 = sec1; |
197 | /* Get the TSSS value */ |
198 | ns = readl_relaxed(ioaddr + PTP_STNSR); |
199 | /* Get the TSS value */ |
200 | sec1 = readl_relaxed(ioaddr + PTP_STSR); |
201 | } while (sec0 != sec1); |
202 | |
203 | if (systime) |
204 | *systime = ns + (sec1 * 1000000000ULL); |
205 | } |
206 | |
207 | static void get_ptptime(void __iomem *ptpaddr, u64 *ptp_time) |
208 | { |
209 | u64 ns; |
210 | |
211 | ns = readl(addr: ptpaddr + PTP_ATNR); |
212 | ns += readl(addr: ptpaddr + PTP_ATSR) * NSEC_PER_SEC; |
213 | |
214 | *ptp_time = ns; |
215 | } |
216 | |
217 | static void timestamp_interrupt(struct stmmac_priv *priv) |
218 | { |
219 | u32 num_snapshot, ts_status, tsync_int; |
220 | struct ptp_clock_event event; |
221 | unsigned long flags; |
222 | u64 ptp_time; |
223 | int i; |
224 | |
225 | if (priv->plat->flags & STMMAC_FLAG_INT_SNAPSHOT_EN) { |
226 | wake_up(&priv->tstamp_busy_wait); |
227 | return; |
228 | } |
229 | |
230 | tsync_int = readl(addr: priv->ioaddr + GMAC_INT_STATUS) & GMAC_INT_TSIE; |
231 | |
232 | if (!tsync_int) |
233 | return; |
234 | |
235 | /* Read timestamp status to clear interrupt from either external |
236 | * timestamp or start/end of PPS. |
237 | */ |
238 | ts_status = readl(addr: priv->ioaddr + GMAC_TIMESTAMP_STATUS); |
239 | |
240 | if (!(priv->plat->flags & STMMAC_FLAG_EXT_SNAPSHOT_EN)) |
241 | return; |
242 | |
243 | num_snapshot = (ts_status & GMAC_TIMESTAMP_ATSNS_MASK) >> |
244 | GMAC_TIMESTAMP_ATSNS_SHIFT; |
245 | |
246 | for (i = 0; i < num_snapshot; i++) { |
247 | read_lock_irqsave(&priv->ptp_lock, flags); |
248 | get_ptptime(ptpaddr: priv->ptpaddr, ptp_time: &ptp_time); |
249 | read_unlock_irqrestore(&priv->ptp_lock, flags); |
250 | event.type = PTP_CLOCK_EXTTS; |
251 | event.index = 0; |
252 | event.timestamp = ptp_time; |
253 | ptp_clock_event(ptp: priv->ptp_clock, event: &event); |
254 | } |
255 | } |
256 | |
257 | const struct stmmac_hwtimestamp stmmac_ptp = { |
258 | .config_hw_tstamping = config_hw_tstamping, |
259 | .init_systime = init_systime, |
260 | .config_sub_second_increment = config_sub_second_increment, |
261 | .config_addend = config_addend, |
262 | .adjust_systime = adjust_systime, |
263 | .get_systime = get_systime, |
264 | .get_ptptime = get_ptptime, |
265 | .timestamp_interrupt = timestamp_interrupt, |
266 | .hwtstamp_correct_latency = hwtstamp_correct_latency, |
267 | }; |
268 | |