summaryrefslogtreecommitdiff
path: root/drivers/emac-dwc-eqos/DWC_ETH_QOS_ptp.c
blob: 80e616fd2def782773e54230b9b5a79d2c544350 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * This file contain content copied from Synopsis driver,
 * provided under the license below
 */
/* =========================================================================
 * The Synopsys DWC ETHER QOS Software Driver and documentation (hereinafter
 * "Software") is an unsupported proprietary work of Synopsys, Inc. unless
 * otherwise expressly agreed to in writing between Synopsys and you.
 *
 * The Software IS NOT an item of Licensed Software or Licensed Product under
 * any End User Software License Agreement or Agreement for Licensed Product
 * with Synopsys or any supplement thereto.  Permission is hereby granted,
 * free of charge, to any person obtaining a copy of this software annotated
 * with this license and the Software, to deal in the Software without
 * restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 * =========================================================================
 */

/*!@file: DWC_ETH_QOS_ptp.c
 * @brief: Driver functions.
 */
#include "DWC_ETH_QOS_yheader.h"
#include "DWC_ETH_QOS_yapphdr.h"

extern int ETH_PPSOUT_Config(struct DWC_ETH_QOS_prv_data *pdata, struct ifr_data_struct* req);
extern void DWC_ETH_QOS_pps_timer_init(struct ifr_data_struct* req);



/*!
 * \brief API to adjust the frequency of hardware clock.
 *
 * \details This function is used to adjust the frequency of the
 * hardware clock.
 *
 * \param[in] ptp – pointer to ptp_clock_info structure.
 * \param[in] delta – desired period change in parts per billion.
 *
 * \return int
 *
 * \retval 0 on success and -ve number on failure.
 */

static int DWC_ETH_QOS_adjust_freq(struct ptp_clock_info *ptp, s32 ppb)
{
	struct DWC_ETH_QOS_prv_data *pdata =
		container_of(ptp, struct DWC_ETH_QOS_prv_data, ptp_clock_ops);
	struct hw_if_struct *hw_if = &pdata->hw_if;
	unsigned long flags;
	u64 adj;
	u32 diff, addend;
	int neg_adj = 0;

	DBGPR_PTP("-->DWC_ETH_QOS_adjust_freq: %d\n", ppb);

	if (ppb < 0) {
		neg_adj = 1;
		ppb = -ppb;
	}

	addend = pdata->default_addend;
	adj = addend;
	adj *= ppb;
	/* div_u64 will divided the "adj" by "1000000000ULL"
	 * and return the quotient.
	 */
	diff = div_u64(adj, 1000000000ULL);
	addend = neg_adj ? (addend - diff) : (addend + diff);

	spin_lock_irqsave(&pdata->ptp_lock, flags);

	hw_if->config_addend(addend);

	spin_unlock_irqrestore(&pdata->ptp_lock, flags);

	DBGPR_PTP("<--DWC_ETH_QOS_adjust_freq\n");

	return 0;
}

/*!
 * \brief API to adjust the hardware time.
 *
 * \details This function is used to shift/adjust the time of the
 * hardware clock.
 *
 * \param[in] ptp – pointer to ptp_clock_info structure.
 * \param[in] delta – desired change in nanoseconds.
 *
 * \return int
 *
 * \retval 0 on success and -ve number on failure.
 */

static int DWC_ETH_QOS_adjust_time(struct ptp_clock_info *ptp, s64 delta)
{
	struct DWC_ETH_QOS_prv_data *pdata =
		container_of(ptp, struct DWC_ETH_QOS_prv_data, ptp_clock_ops);
	struct hw_if_struct *hw_if = &pdata->hw_if;
	unsigned long flags;
	u32 sec, nsec;
	u32 quotient, reminder;
	int neg_adj = 0;

	DBGPR_PTP("-->DWC_ETH_QOS_adjust_time: delta = %lld\n", delta);

	if (delta < 0) {
		neg_adj = 1;
		delta = -delta;
	}

	quotient = div_u64_rem(delta, 1000000000ULL, &reminder);
	sec = quotient;
	nsec = reminder;

	spin_lock_irqsave(&pdata->ptp_lock, flags);

	hw_if->adjust_systime(sec, nsec, neg_adj, pdata->one_nsec_accuracy);

	spin_unlock_irqrestore(&pdata->ptp_lock, flags);

	DBGPR_PTP("<--DWC_ETH_QOS_adjust_time\n");

	return 0;
}

/*!
 * \brief API to get the current time.
 *
 * \details This function is used to read the current time from the
 * hardware clock.
 *
 * \param[in] ptp – pointer to ptp_clock_info structure.
 * \param[in] ts – pointer to hold the time/result.
 *
 * \return int
 *
 * \retval 0 on success and -ve number on failure.
 */

static int DWC_ETH_QOS_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
	struct DWC_ETH_QOS_prv_data *pdata =
		container_of(ptp, struct DWC_ETH_QOS_prv_data, ptp_clock_ops);
	struct hw_if_struct *hw_if = &pdata->hw_if;
	u64 ns;
	u32 reminder;
	unsigned long flags;

	DBGPR_PTP("-->DWC_ETH_QOS_get_time\n");

	spin_lock_irqsave(&pdata->ptp_lock, flags);

	ns = hw_if->get_systime();

	spin_unlock_irqrestore(&pdata->ptp_lock, flags);

	ts->tv_sec = div_u64_rem(ns, 1000000000ULL, &reminder);
	ts->tv_nsec = reminder;

	DBGPR_PTP("<--DWC_ETH_QOS_get_time: ts->tv_sec = %ld,", ts->tv_sec);
	DBGPR_PTP("ts->tv_nsec = %ld\n", ts->tv_nsec);

	return 0;
}

/*!
 * \brief API to set the current time.
 *
 * \details This function is used to set the current time on the
 * hardware clock.
 *
 * \param[in] ptp – pointer to ptp_clock_info structure.
 * \param[in] ts – time value to set.
 *
 * \return int
 *
 * \retval 0 on success and -ve number on failure.
 */

static int DWC_ETH_QOS_set_time(struct ptp_clock_info *ptp,
				const struct timespec64 *ts)
{
	struct DWC_ETH_QOS_prv_data *pdata =
		container_of(ptp, struct DWC_ETH_QOS_prv_data, ptp_clock_ops);
	struct hw_if_struct *hw_if = &pdata->hw_if;
	unsigned long flags;

	DBGPR_PTP("-->DWC_ETH_QOS_set_time: ts->tv_sec = %ld,", ts->tv_sec);
	DBGPR_PTP("ts->tv_nsec = %ld\n", ts->tv_nsec);

	spin_lock_irqsave(&pdata->ptp_lock, flags);

	hw_if->init_systime(ts->tv_sec, ts->tv_nsec);

	spin_unlock_irqrestore(&pdata->ptp_lock, flags);

	DBGPR_PTP("<--DWC_ETH_QOS_set_time\n");

	return 0;
}

/*!
 * \brief API to enable/disable an ancillary feature.
 *
 * \details This function is used to enable or disable an ancillary
 * device feature like PPS, PEROUT and EXTTS.
 *
 * \param[in] ptp – pointer to ptp_clock_info structure.
 * \param[in] rq – desired resource to enable or disable.
 * \param[in] on – caller passes one to enable or zero to disable.
 *
 * \return int
 *
 * \retval 0 on success and -ve(EINVAL or EOPNOTSUPP) number on failure.
 */

static int DWC_ETH_QOS_enable(struct ptp_clock_info *ptp,
			      struct ptp_clock_request *rq, int on)
{
	return -EOPNOTSUPP;
}

/* structure describing a PTP hardware clock.
 */
static struct ptp_clock_info DWC_ETH_QOS_ptp_clock_ops = {
	.owner = THIS_MODULE,
	.name = "DWC_ETH_QOS_clk",
	.max_adj = DWC_ETH_QOS_SYSCLOCK,
	/* the max possible frequency adjustment, in parts per billion */
	.n_alarm = 0,	/* the number of programmable alarms */
	.n_ext_ts = 0,	/* the number of externel time stamp channels */
	.n_per_out = 0, /* the number of programmable periodic signals */
	.pps = 0,	/* indicates whether the clk supports a PPS callback */
	.adjfreq = DWC_ETH_QOS_adjust_freq,
	.adjtime = DWC_ETH_QOS_adjust_time,
	.gettime64 = DWC_ETH_QOS_get_time,
	.settime64 = DWC_ETH_QOS_set_time,
	.enable = DWC_ETH_QOS_enable,
};

/*!
 * \brief API to register ptp clock driver.
 *
 * \details This function is used to register the ptp clock
 * driver to kernel. It also does some housekeeping work.
 *
 * \param[in] pdata – pointer to private data structure.
 *
 * \return int
 *
 * \retval 0 on success and -ve number on failure.
 */

int DWC_ETH_QOS_ptp_init(struct DWC_ETH_QOS_prv_data *pdata)
{
	int ret = 0;
	struct ifr_data_struct req = {0};
	struct ETH_PPS_Config eth_pps_cfg = {0};

	DBGPR_PTP("-->DWC_ETH_QOS_ptp_init\n");

	if (!pdata->hw_feat.tsstssel) {
		ret = -1;
		pdata->ptp_clock = NULL;
		pr_alert("No PTP supports in HW\n"
			"Aborting PTP clock driver registration\n");
		goto no_hw_ptp;
	}

	DWC_ETH_QOS_enable_ptp_clk(&pdata->pdev->dev);

	spin_lock_init(&pdata->ptp_lock);

	pdata->ptp_clock_ops = DWC_ETH_QOS_ptp_clock_ops;

	pdata->ptp_clock = ptp_clock_register(&pdata->ptp_clock_ops,
						&pdata->pdev->dev);

	if (IS_ERR(pdata->ptp_clock)) {
		pdata->ptp_clock = NULL;
		pr_alert("ptp_clock_register() failed\n");
	} else {
		pr_alert("Added PTP HW clock successfully\n");
	}
	if (pdata->emac_hw_version_type == EMAC_HW_v2_3_1) {
		/*Configuaring PPS0 PPS output frequency to defualt 19.2 Mhz*/
		eth_pps_cfg.ppsout_ch = 0;
		eth_pps_cfg.ptpclk_freq == DWC_ETH_QOS_DEFAULT_PTP_CLOCK;
		eth_pps_cfg.ppsout_freq = 19200000;
		eth_pps_cfg.ppsout_start = 1;
		req.ptr = (void*)&eth_pps_cfg;

		DWC_ETH_QOS_pps_timer_init(&req);
		ret = ETH_PPSOUT_Config(pdata, &req);
	}

	DBGPR_PTP("<--DWC_ETH_QOS_ptp_init\n");

	return ret;

no_hw_ptp:
	return ret;
}

/*!
 * \brief API to unregister ptp clock driver.
 *
 * \details This function is used to remove/unregister the ptp
 * clock driver from the kernel.
 *
 * \param[in] pdata – pointer to private data structure.
 *
 * \return void
 */

void DWC_ETH_QOS_ptp_remove(struct DWC_ETH_QOS_prv_data *pdata)
{
	DBGPR_PTP("-->DWC_ETH_QOS_ptp_remove\n");

	if (pdata->ptp_clock) {
		ptp_clock_unregister(pdata->ptp_clock);
		pr_alert("Removed PTP HW clock successfully\n");
	}

	DWC_ETH_QOS_disable_ptp_clk(&pdata->pdev->dev);

	DBGPR_PTP("<--DWC_ETH_QOS_ptp_remove\n");
}
/*!
 * \brief API to find the PHC index.
 *
 * \details This function is used to find the PHC index from Linux subsystem
 *
 * \param[in] pdata ??? pointer to private data structure.
 *
 * \return void
 */

int DWC_ETH_QOS_phc_index(struct DWC_ETH_QOS_prv_data *pdata)
{
	DBGPR_PTP("Fetching PHC index \n");
	if (pdata->ptp_clock)
		return ptp_clock_index(pdata->ptp_clock);
	else
		return -1;
}