162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/******************************************************************************* 362306a36Sopenharmony_ci PTP 1588 clock using the STMMAC. 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci Copyright (C) 2013 Vayavya Labs Pvt Ltd 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci Author: Rayagond Kokatanur <rayagond@vayavyalabs.com> 962306a36Sopenharmony_ci*******************************************************************************/ 1062306a36Sopenharmony_ci#include "stmmac.h" 1162306a36Sopenharmony_ci#include "stmmac_ptp.h" 1262306a36Sopenharmony_ci#include "dwmac4.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/** 1562306a36Sopenharmony_ci * stmmac_adjust_freq 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * @ptp: pointer to ptp_clock_info structure 1862306a36Sopenharmony_ci * @scaled_ppm: desired period change in scaled parts per million 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * Description: this function will adjust the frequency of hardware clock. 2162306a36Sopenharmony_ci * 2262306a36Sopenharmony_ci * Scaled parts per million is ppm with a 16-bit binary fractional field. 2362306a36Sopenharmony_ci */ 2462306a36Sopenharmony_cistatic int stmmac_adjust_freq(struct ptp_clock_info *ptp, long scaled_ppm) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci struct stmmac_priv *priv = 2762306a36Sopenharmony_ci container_of(ptp, struct stmmac_priv, ptp_clock_ops); 2862306a36Sopenharmony_ci unsigned long flags; 2962306a36Sopenharmony_ci u32 addend; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci addend = adjust_by_scaled_ppm(priv->default_addend, scaled_ppm); 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci write_lock_irqsave(&priv->ptp_lock, flags); 3462306a36Sopenharmony_ci stmmac_config_addend(priv, priv->ptpaddr, addend); 3562306a36Sopenharmony_ci write_unlock_irqrestore(&priv->ptp_lock, flags); 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci return 0; 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/** 4162306a36Sopenharmony_ci * stmmac_adjust_time 4262306a36Sopenharmony_ci * 4362306a36Sopenharmony_ci * @ptp: pointer to ptp_clock_info structure 4462306a36Sopenharmony_ci * @delta: desired change in nanoseconds 4562306a36Sopenharmony_ci * 4662306a36Sopenharmony_ci * Description: this function will shift/adjust the hardware clock time. 4762306a36Sopenharmony_ci */ 4862306a36Sopenharmony_cistatic int stmmac_adjust_time(struct ptp_clock_info *ptp, s64 delta) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci struct stmmac_priv *priv = 5162306a36Sopenharmony_ci container_of(ptp, struct stmmac_priv, ptp_clock_ops); 5262306a36Sopenharmony_ci unsigned long flags; 5362306a36Sopenharmony_ci u32 sec, nsec; 5462306a36Sopenharmony_ci u32 quotient, reminder; 5562306a36Sopenharmony_ci int neg_adj = 0; 5662306a36Sopenharmony_ci bool xmac, est_rst = false; 5762306a36Sopenharmony_ci int ret; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci xmac = priv->plat->has_gmac4 || priv->plat->has_xgmac; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (delta < 0) { 6262306a36Sopenharmony_ci neg_adj = 1; 6362306a36Sopenharmony_ci delta = -delta; 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci quotient = div_u64_rem(delta, 1000000000ULL, &reminder); 6762306a36Sopenharmony_ci sec = quotient; 6862306a36Sopenharmony_ci nsec = reminder; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* If EST is enabled, disabled it before adjust ptp time. */ 7162306a36Sopenharmony_ci if (priv->plat->est && priv->plat->est->enable) { 7262306a36Sopenharmony_ci est_rst = true; 7362306a36Sopenharmony_ci mutex_lock(&priv->plat->est->lock); 7462306a36Sopenharmony_ci priv->plat->est->enable = false; 7562306a36Sopenharmony_ci stmmac_est_configure(priv, priv->ioaddr, priv->plat->est, 7662306a36Sopenharmony_ci priv->plat->clk_ptp_rate); 7762306a36Sopenharmony_ci mutex_unlock(&priv->plat->est->lock); 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci write_lock_irqsave(&priv->ptp_lock, flags); 8162306a36Sopenharmony_ci stmmac_adjust_systime(priv, priv->ptpaddr, sec, nsec, neg_adj, xmac); 8262306a36Sopenharmony_ci write_unlock_irqrestore(&priv->ptp_lock, flags); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci /* Caculate new basetime and re-configured EST after PTP time adjust. */ 8562306a36Sopenharmony_ci if (est_rst) { 8662306a36Sopenharmony_ci struct timespec64 current_time, time; 8762306a36Sopenharmony_ci ktime_t current_time_ns, basetime; 8862306a36Sopenharmony_ci u64 cycle_time; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci mutex_lock(&priv->plat->est->lock); 9162306a36Sopenharmony_ci priv->ptp_clock_ops.gettime64(&priv->ptp_clock_ops, ¤t_time); 9262306a36Sopenharmony_ci current_time_ns = timespec64_to_ktime(current_time); 9362306a36Sopenharmony_ci time.tv_nsec = priv->plat->est->btr_reserve[0]; 9462306a36Sopenharmony_ci time.tv_sec = priv->plat->est->btr_reserve[1]; 9562306a36Sopenharmony_ci basetime = timespec64_to_ktime(time); 9662306a36Sopenharmony_ci cycle_time = (u64)priv->plat->est->ctr[1] * NSEC_PER_SEC + 9762306a36Sopenharmony_ci priv->plat->est->ctr[0]; 9862306a36Sopenharmony_ci time = stmmac_calc_tas_basetime(basetime, 9962306a36Sopenharmony_ci current_time_ns, 10062306a36Sopenharmony_ci cycle_time); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci priv->plat->est->btr[0] = (u32)time.tv_nsec; 10362306a36Sopenharmony_ci priv->plat->est->btr[1] = (u32)time.tv_sec; 10462306a36Sopenharmony_ci priv->plat->est->enable = true; 10562306a36Sopenharmony_ci ret = stmmac_est_configure(priv, priv->ioaddr, priv->plat->est, 10662306a36Sopenharmony_ci priv->plat->clk_ptp_rate); 10762306a36Sopenharmony_ci mutex_unlock(&priv->plat->est->lock); 10862306a36Sopenharmony_ci if (ret) 10962306a36Sopenharmony_ci netdev_err(priv->dev, "failed to configure EST\n"); 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci return 0; 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci/** 11662306a36Sopenharmony_ci * stmmac_get_time 11762306a36Sopenharmony_ci * 11862306a36Sopenharmony_ci * @ptp: pointer to ptp_clock_info structure 11962306a36Sopenharmony_ci * @ts: pointer to hold time/result 12062306a36Sopenharmony_ci * 12162306a36Sopenharmony_ci * Description: this function will read the current time from the 12262306a36Sopenharmony_ci * hardware clock and store it in @ts. 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_cistatic int stmmac_get_time(struct ptp_clock_info *ptp, struct timespec64 *ts) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct stmmac_priv *priv = 12762306a36Sopenharmony_ci container_of(ptp, struct stmmac_priv, ptp_clock_ops); 12862306a36Sopenharmony_ci unsigned long flags; 12962306a36Sopenharmony_ci u64 ns = 0; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci read_lock_irqsave(&priv->ptp_lock, flags); 13262306a36Sopenharmony_ci stmmac_get_systime(priv, priv->ptpaddr, &ns); 13362306a36Sopenharmony_ci read_unlock_irqrestore(&priv->ptp_lock, flags); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci *ts = ns_to_timespec64(ns); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return 0; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci/** 14162306a36Sopenharmony_ci * stmmac_set_time 14262306a36Sopenharmony_ci * 14362306a36Sopenharmony_ci * @ptp: pointer to ptp_clock_info structure 14462306a36Sopenharmony_ci * @ts: time value to set 14562306a36Sopenharmony_ci * 14662306a36Sopenharmony_ci * Description: this function will set the current time on the 14762306a36Sopenharmony_ci * hardware clock. 14862306a36Sopenharmony_ci */ 14962306a36Sopenharmony_cistatic int stmmac_set_time(struct ptp_clock_info *ptp, 15062306a36Sopenharmony_ci const struct timespec64 *ts) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci struct stmmac_priv *priv = 15362306a36Sopenharmony_ci container_of(ptp, struct stmmac_priv, ptp_clock_ops); 15462306a36Sopenharmony_ci unsigned long flags; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci write_lock_irqsave(&priv->ptp_lock, flags); 15762306a36Sopenharmony_ci stmmac_init_systime(priv, priv->ptpaddr, ts->tv_sec, ts->tv_nsec); 15862306a36Sopenharmony_ci write_unlock_irqrestore(&priv->ptp_lock, flags); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int stmmac_enable(struct ptp_clock_info *ptp, 16462306a36Sopenharmony_ci struct ptp_clock_request *rq, int on) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct stmmac_priv *priv = 16762306a36Sopenharmony_ci container_of(ptp, struct stmmac_priv, ptp_clock_ops); 16862306a36Sopenharmony_ci void __iomem *ptpaddr = priv->ptpaddr; 16962306a36Sopenharmony_ci struct stmmac_pps_cfg *cfg; 17062306a36Sopenharmony_ci int ret = -EOPNOTSUPP; 17162306a36Sopenharmony_ci unsigned long flags; 17262306a36Sopenharmony_ci u32 acr_value; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci switch (rq->type) { 17562306a36Sopenharmony_ci case PTP_CLK_REQ_PEROUT: 17662306a36Sopenharmony_ci /* Reject requests with unsupported flags */ 17762306a36Sopenharmony_ci if (rq->perout.flags) 17862306a36Sopenharmony_ci return -EOPNOTSUPP; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci cfg = &priv->pps[rq->perout.index]; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci cfg->start.tv_sec = rq->perout.start.sec; 18362306a36Sopenharmony_ci cfg->start.tv_nsec = rq->perout.start.nsec; 18462306a36Sopenharmony_ci cfg->period.tv_sec = rq->perout.period.sec; 18562306a36Sopenharmony_ci cfg->period.tv_nsec = rq->perout.period.nsec; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci write_lock_irqsave(&priv->ptp_lock, flags); 18862306a36Sopenharmony_ci ret = stmmac_flex_pps_config(priv, priv->ioaddr, 18962306a36Sopenharmony_ci rq->perout.index, cfg, on, 19062306a36Sopenharmony_ci priv->sub_second_inc, 19162306a36Sopenharmony_ci priv->systime_flags); 19262306a36Sopenharmony_ci write_unlock_irqrestore(&priv->ptp_lock, flags); 19362306a36Sopenharmony_ci break; 19462306a36Sopenharmony_ci case PTP_CLK_REQ_EXTTS: 19562306a36Sopenharmony_ci if (on) 19662306a36Sopenharmony_ci priv->plat->flags |= STMMAC_FLAG_EXT_SNAPSHOT_EN; 19762306a36Sopenharmony_ci else 19862306a36Sopenharmony_ci priv->plat->flags &= ~STMMAC_FLAG_EXT_SNAPSHOT_EN; 19962306a36Sopenharmony_ci mutex_lock(&priv->aux_ts_lock); 20062306a36Sopenharmony_ci acr_value = readl(ptpaddr + PTP_ACR); 20162306a36Sopenharmony_ci acr_value &= ~PTP_ACR_MASK; 20262306a36Sopenharmony_ci if (on) { 20362306a36Sopenharmony_ci /* Enable External snapshot trigger */ 20462306a36Sopenharmony_ci acr_value |= priv->plat->ext_snapshot_num; 20562306a36Sopenharmony_ci acr_value |= PTP_ACR_ATSFC; 20662306a36Sopenharmony_ci netdev_dbg(priv->dev, "Auxiliary Snapshot %d enabled.\n", 20762306a36Sopenharmony_ci priv->plat->ext_snapshot_num >> 20862306a36Sopenharmony_ci PTP_ACR_ATSEN_SHIFT); 20962306a36Sopenharmony_ci } else { 21062306a36Sopenharmony_ci netdev_dbg(priv->dev, "Auxiliary Snapshot %d disabled.\n", 21162306a36Sopenharmony_ci priv->plat->ext_snapshot_num >> 21262306a36Sopenharmony_ci PTP_ACR_ATSEN_SHIFT); 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci writel(acr_value, ptpaddr + PTP_ACR); 21562306a36Sopenharmony_ci mutex_unlock(&priv->aux_ts_lock); 21662306a36Sopenharmony_ci /* wait for auxts fifo clear to finish */ 21762306a36Sopenharmony_ci ret = readl_poll_timeout(ptpaddr + PTP_ACR, acr_value, 21862306a36Sopenharmony_ci !(acr_value & PTP_ACR_ATSFC), 21962306a36Sopenharmony_ci 10, 10000); 22062306a36Sopenharmony_ci break; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci default: 22362306a36Sopenharmony_ci break; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci return ret; 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci/** 23062306a36Sopenharmony_ci * stmmac_get_syncdevicetime 23162306a36Sopenharmony_ci * @device: current device time 23262306a36Sopenharmony_ci * @system: system counter value read synchronously with device time 23362306a36Sopenharmony_ci * @ctx: context provided by timekeeping code 23462306a36Sopenharmony_ci * Description: Read device and system clock simultaneously and return the 23562306a36Sopenharmony_ci * corrected clock values in ns. 23662306a36Sopenharmony_ci **/ 23762306a36Sopenharmony_cistatic int stmmac_get_syncdevicetime(ktime_t *device, 23862306a36Sopenharmony_ci struct system_counterval_t *system, 23962306a36Sopenharmony_ci void *ctx) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci struct stmmac_priv *priv = (struct stmmac_priv *)ctx; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (priv->plat->crosststamp) 24462306a36Sopenharmony_ci return priv->plat->crosststamp(device, system, ctx); 24562306a36Sopenharmony_ci else 24662306a36Sopenharmony_ci return -EOPNOTSUPP; 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic int stmmac_getcrosststamp(struct ptp_clock_info *ptp, 25062306a36Sopenharmony_ci struct system_device_crosststamp *xtstamp) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct stmmac_priv *priv = 25362306a36Sopenharmony_ci container_of(ptp, struct stmmac_priv, ptp_clock_ops); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci return get_device_system_crosststamp(stmmac_get_syncdevicetime, 25662306a36Sopenharmony_ci priv, NULL, xtstamp); 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci/* structure describing a PTP hardware clock */ 26062306a36Sopenharmony_cistatic struct ptp_clock_info stmmac_ptp_clock_ops = { 26162306a36Sopenharmony_ci .owner = THIS_MODULE, 26262306a36Sopenharmony_ci .name = "stmmac ptp", 26362306a36Sopenharmony_ci .max_adj = 62500000, 26462306a36Sopenharmony_ci .n_alarm = 0, 26562306a36Sopenharmony_ci .n_ext_ts = 0, /* will be overwritten in stmmac_ptp_register */ 26662306a36Sopenharmony_ci .n_per_out = 0, /* will be overwritten in stmmac_ptp_register */ 26762306a36Sopenharmony_ci .n_pins = 0, 26862306a36Sopenharmony_ci .pps = 0, 26962306a36Sopenharmony_ci .adjfine = stmmac_adjust_freq, 27062306a36Sopenharmony_ci .adjtime = stmmac_adjust_time, 27162306a36Sopenharmony_ci .gettime64 = stmmac_get_time, 27262306a36Sopenharmony_ci .settime64 = stmmac_set_time, 27362306a36Sopenharmony_ci .enable = stmmac_enable, 27462306a36Sopenharmony_ci .getcrosststamp = stmmac_getcrosststamp, 27562306a36Sopenharmony_ci}; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci/** 27862306a36Sopenharmony_ci * stmmac_ptp_register 27962306a36Sopenharmony_ci * @priv: driver private structure 28062306a36Sopenharmony_ci * Description: this function will register the ptp clock driver 28162306a36Sopenharmony_ci * to kernel. It also does some house keeping work. 28262306a36Sopenharmony_ci */ 28362306a36Sopenharmony_civoid stmmac_ptp_register(struct stmmac_priv *priv) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci int i; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci for (i = 0; i < priv->dma_cap.pps_out_num; i++) { 28862306a36Sopenharmony_ci if (i >= STMMAC_PPS_MAX) 28962306a36Sopenharmony_ci break; 29062306a36Sopenharmony_ci priv->pps[i].available = true; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci if (priv->plat->ptp_max_adj) 29462306a36Sopenharmony_ci stmmac_ptp_clock_ops.max_adj = priv->plat->ptp_max_adj; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci /* Calculate the clock domain crossing (CDC) error if necessary */ 29762306a36Sopenharmony_ci priv->plat->cdc_error_adj = 0; 29862306a36Sopenharmony_ci if (priv->plat->has_gmac4 && priv->plat->clk_ptp_rate) 29962306a36Sopenharmony_ci priv->plat->cdc_error_adj = (2 * NSEC_PER_SEC) / priv->plat->clk_ptp_rate; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci stmmac_ptp_clock_ops.n_per_out = priv->dma_cap.pps_out_num; 30262306a36Sopenharmony_ci stmmac_ptp_clock_ops.n_ext_ts = priv->dma_cap.aux_snapshot_n; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci rwlock_init(&priv->ptp_lock); 30562306a36Sopenharmony_ci mutex_init(&priv->aux_ts_lock); 30662306a36Sopenharmony_ci priv->ptp_clock_ops = stmmac_ptp_clock_ops; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci priv->ptp_clock = ptp_clock_register(&priv->ptp_clock_ops, 30962306a36Sopenharmony_ci priv->device); 31062306a36Sopenharmony_ci if (IS_ERR(priv->ptp_clock)) { 31162306a36Sopenharmony_ci netdev_err(priv->dev, "ptp_clock_register failed\n"); 31262306a36Sopenharmony_ci priv->ptp_clock = NULL; 31362306a36Sopenharmony_ci } else if (priv->ptp_clock) 31462306a36Sopenharmony_ci netdev_info(priv->dev, "registered PTP clock\n"); 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci/** 31862306a36Sopenharmony_ci * stmmac_ptp_unregister 31962306a36Sopenharmony_ci * @priv: driver private structure 32062306a36Sopenharmony_ci * Description: this function will remove/unregister the ptp clock driver 32162306a36Sopenharmony_ci * from the kernel. 32262306a36Sopenharmony_ci */ 32362306a36Sopenharmony_civoid stmmac_ptp_unregister(struct stmmac_priv *priv) 32462306a36Sopenharmony_ci{ 32562306a36Sopenharmony_ci if (priv->ptp_clock) { 32662306a36Sopenharmony_ci ptp_clock_unregister(priv->ptp_clock); 32762306a36Sopenharmony_ci priv->ptp_clock = NULL; 32862306a36Sopenharmony_ci pr_debug("Removed PTP HW clock successfully on %s\n", 32962306a36Sopenharmony_ci priv->dev->name); 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci mutex_destroy(&priv->aux_ts_lock); 33362306a36Sopenharmony_ci} 334