162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* cavium_ptp.c - PTP 1588 clock on Cavium hardware 362306a36Sopenharmony_ci * Copyright (c) 2003-2015, 2017 Cavium, Inc. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/device.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/timecounter.h> 962306a36Sopenharmony_ci#include <linux/pci.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include "cavium_ptp.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#define DRV_NAME "cavium_ptp" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define PCI_DEVICE_ID_CAVIUM_PTP 0xA00C 1662306a36Sopenharmony_ci#define PCI_SUBSYS_DEVID_88XX_PTP 0xA10C 1762306a36Sopenharmony_ci#define PCI_SUBSYS_DEVID_81XX_PTP 0XA20C 1862306a36Sopenharmony_ci#define PCI_SUBSYS_DEVID_83XX_PTP 0xA30C 1962306a36Sopenharmony_ci#define PCI_DEVICE_ID_CAVIUM_RST 0xA00E 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define PCI_PTP_BAR_NO 0 2262306a36Sopenharmony_ci#define PCI_RST_BAR_NO 0 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#define PTP_CLOCK_CFG 0xF00ULL 2562306a36Sopenharmony_ci#define PTP_CLOCK_CFG_PTP_EN BIT(0) 2662306a36Sopenharmony_ci#define PTP_CLOCK_LO 0xF08ULL 2762306a36Sopenharmony_ci#define PTP_CLOCK_HI 0xF10ULL 2862306a36Sopenharmony_ci#define PTP_CLOCK_COMP 0xF18ULL 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define RST_BOOT 0x1600ULL 3162306a36Sopenharmony_ci#define CLOCK_BASE_RATE 50000000ULL 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic u64 ptp_cavium_clock_get(void) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci struct pci_dev *pdev; 3662306a36Sopenharmony_ci void __iomem *base; 3762306a36Sopenharmony_ci u64 ret = CLOCK_BASE_RATE * 16; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM, 4062306a36Sopenharmony_ci PCI_DEVICE_ID_CAVIUM_RST, NULL); 4162306a36Sopenharmony_ci if (!pdev) 4262306a36Sopenharmony_ci goto error; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci base = pci_ioremap_bar(pdev, PCI_RST_BAR_NO); 4562306a36Sopenharmony_ci if (!base) 4662306a36Sopenharmony_ci goto error_put_pdev; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci ret = CLOCK_BASE_RATE * ((readq(base + RST_BOOT) >> 33) & 0x3f); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci iounmap(base); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cierror_put_pdev: 5362306a36Sopenharmony_ci pci_dev_put(pdev); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cierror: 5662306a36Sopenharmony_ci return ret; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistruct cavium_ptp *cavium_ptp_get(void) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct cavium_ptp *ptp; 6262306a36Sopenharmony_ci struct pci_dev *pdev; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM, 6562306a36Sopenharmony_ci PCI_DEVICE_ID_CAVIUM_PTP, NULL); 6662306a36Sopenharmony_ci if (!pdev) 6762306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci ptp = pci_get_drvdata(pdev); 7062306a36Sopenharmony_ci if (!ptp) 7162306a36Sopenharmony_ci ptp = ERR_PTR(-EPROBE_DEFER); 7262306a36Sopenharmony_ci if (IS_ERR(ptp)) 7362306a36Sopenharmony_ci pci_dev_put(pdev); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci return ptp; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ciEXPORT_SYMBOL(cavium_ptp_get); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_civoid cavium_ptp_put(struct cavium_ptp *ptp) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci if (!ptp) 8262306a36Sopenharmony_ci return; 8362306a36Sopenharmony_ci pci_dev_put(ptp->pdev); 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ciEXPORT_SYMBOL(cavium_ptp_put); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci/** 8862306a36Sopenharmony_ci * cavium_ptp_adjfine() - Adjust ptp frequency 8962306a36Sopenharmony_ci * @ptp_info: PTP clock info 9062306a36Sopenharmony_ci * @scaled_ppm: how much to adjust by, in parts per million, but with a 9162306a36Sopenharmony_ci * 16 bit binary fractional field 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_cistatic int cavium_ptp_adjfine(struct ptp_clock_info *ptp_info, long scaled_ppm) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci struct cavium_ptp *clock = 9662306a36Sopenharmony_ci container_of(ptp_info, struct cavium_ptp, ptp_info); 9762306a36Sopenharmony_ci unsigned long flags; 9862306a36Sopenharmony_ci u64 comp; 9962306a36Sopenharmony_ci u64 adj; 10062306a36Sopenharmony_ci bool neg_adj = false; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci if (scaled_ppm < 0) { 10362306a36Sopenharmony_ci neg_adj = true; 10462306a36Sopenharmony_ci scaled_ppm = -scaled_ppm; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci /* The hardware adds the clock compensation value to the PTP clock 10862306a36Sopenharmony_ci * on every coprocessor clock cycle. Typical convention is that it 10962306a36Sopenharmony_ci * represent number of nanosecond betwen each cycle. In this 11062306a36Sopenharmony_ci * convention compensation value is in 64 bit fixed-point 11162306a36Sopenharmony_ci * representation where upper 32 bits are number of nanoseconds 11262306a36Sopenharmony_ci * and lower is fractions of nanosecond. 11362306a36Sopenharmony_ci * The scaled_ppm represent the ratio in "parts per bilion" by which the 11462306a36Sopenharmony_ci * compensation value should be corrected. 11562306a36Sopenharmony_ci * To calculate new compenstation value we use 64bit fixed point 11662306a36Sopenharmony_ci * arithmetic on following formula 11762306a36Sopenharmony_ci * comp = tbase + tbase * scaled_ppm / (1M * 2^16) 11862306a36Sopenharmony_ci * where tbase is the basic compensation value calculated initialy 11962306a36Sopenharmony_ci * in cavium_ptp_init() -> tbase = 1/Hz. Then we use endian 12062306a36Sopenharmony_ci * independent structure definition to write data to PTP register. 12162306a36Sopenharmony_ci */ 12262306a36Sopenharmony_ci comp = ((u64)1000000000ull << 32) / clock->clock_rate; 12362306a36Sopenharmony_ci adj = comp * scaled_ppm; 12462306a36Sopenharmony_ci adj >>= 16; 12562306a36Sopenharmony_ci adj = div_u64(adj, 1000000ull); 12662306a36Sopenharmony_ci comp = neg_adj ? comp - adj : comp + adj; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci spin_lock_irqsave(&clock->spin_lock, flags); 12962306a36Sopenharmony_ci writeq(comp, clock->reg_base + PTP_CLOCK_COMP); 13062306a36Sopenharmony_ci spin_unlock_irqrestore(&clock->spin_lock, flags); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci return 0; 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci/** 13662306a36Sopenharmony_ci * cavium_ptp_adjtime() - Adjust ptp time 13762306a36Sopenharmony_ci * @ptp_info: PTP clock info 13862306a36Sopenharmony_ci * @delta: how much to adjust by, in nanosecs 13962306a36Sopenharmony_ci */ 14062306a36Sopenharmony_cistatic int cavium_ptp_adjtime(struct ptp_clock_info *ptp_info, s64 delta) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct cavium_ptp *clock = 14362306a36Sopenharmony_ci container_of(ptp_info, struct cavium_ptp, ptp_info); 14462306a36Sopenharmony_ci unsigned long flags; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci spin_lock_irqsave(&clock->spin_lock, flags); 14762306a36Sopenharmony_ci timecounter_adjtime(&clock->time_counter, delta); 14862306a36Sopenharmony_ci spin_unlock_irqrestore(&clock->spin_lock, flags); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci /* Sync, for network driver to get latest value */ 15162306a36Sopenharmony_ci smp_mb(); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return 0; 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci/** 15762306a36Sopenharmony_ci * cavium_ptp_gettime() - Get hardware clock time with adjustment 15862306a36Sopenharmony_ci * @ptp_info: PTP clock info 15962306a36Sopenharmony_ci * @ts: timespec 16062306a36Sopenharmony_ci */ 16162306a36Sopenharmony_cistatic int cavium_ptp_gettime(struct ptp_clock_info *ptp_info, 16262306a36Sopenharmony_ci struct timespec64 *ts) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci struct cavium_ptp *clock = 16562306a36Sopenharmony_ci container_of(ptp_info, struct cavium_ptp, ptp_info); 16662306a36Sopenharmony_ci unsigned long flags; 16762306a36Sopenharmony_ci u64 nsec; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci spin_lock_irqsave(&clock->spin_lock, flags); 17062306a36Sopenharmony_ci nsec = timecounter_read(&clock->time_counter); 17162306a36Sopenharmony_ci spin_unlock_irqrestore(&clock->spin_lock, flags); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci *ts = ns_to_timespec64(nsec); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci return 0; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci/** 17962306a36Sopenharmony_ci * cavium_ptp_settime() - Set hardware clock time. Reset adjustment 18062306a36Sopenharmony_ci * @ptp_info: PTP clock info 18162306a36Sopenharmony_ci * @ts: timespec 18262306a36Sopenharmony_ci */ 18362306a36Sopenharmony_cistatic int cavium_ptp_settime(struct ptp_clock_info *ptp_info, 18462306a36Sopenharmony_ci const struct timespec64 *ts) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct cavium_ptp *clock = 18762306a36Sopenharmony_ci container_of(ptp_info, struct cavium_ptp, ptp_info); 18862306a36Sopenharmony_ci unsigned long flags; 18962306a36Sopenharmony_ci u64 nsec; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci nsec = timespec64_to_ns(ts); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci spin_lock_irqsave(&clock->spin_lock, flags); 19462306a36Sopenharmony_ci timecounter_init(&clock->time_counter, &clock->cycle_counter, nsec); 19562306a36Sopenharmony_ci spin_unlock_irqrestore(&clock->spin_lock, flags); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci return 0; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci/** 20162306a36Sopenharmony_ci * cavium_ptp_enable() - Request to enable or disable an ancillary feature. 20262306a36Sopenharmony_ci * @ptp_info: PTP clock info 20362306a36Sopenharmony_ci * @rq: request 20462306a36Sopenharmony_ci * @on: is it on 20562306a36Sopenharmony_ci */ 20662306a36Sopenharmony_cistatic int cavium_ptp_enable(struct ptp_clock_info *ptp_info, 20762306a36Sopenharmony_ci struct ptp_clock_request *rq, int on) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci return -EOPNOTSUPP; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic u64 cavium_ptp_cc_read(const struct cyclecounter *cc) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci struct cavium_ptp *clock = 21562306a36Sopenharmony_ci container_of(cc, struct cavium_ptp, cycle_counter); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci return readq(clock->reg_base + PTP_CLOCK_HI); 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic int cavium_ptp_probe(struct pci_dev *pdev, 22162306a36Sopenharmony_ci const struct pci_device_id *ent) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci struct device *dev = &pdev->dev; 22462306a36Sopenharmony_ci struct cavium_ptp *clock; 22562306a36Sopenharmony_ci struct cyclecounter *cc; 22662306a36Sopenharmony_ci u64 clock_cfg; 22762306a36Sopenharmony_ci u64 clock_comp; 22862306a36Sopenharmony_ci int err; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci clock = devm_kzalloc(dev, sizeof(*clock), GFP_KERNEL); 23162306a36Sopenharmony_ci if (!clock) { 23262306a36Sopenharmony_ci err = -ENOMEM; 23362306a36Sopenharmony_ci goto error; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci clock->pdev = pdev; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci err = pcim_enable_device(pdev); 23962306a36Sopenharmony_ci if (err) 24062306a36Sopenharmony_ci goto error_free; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci err = pcim_iomap_regions(pdev, 1 << PCI_PTP_BAR_NO, pci_name(pdev)); 24362306a36Sopenharmony_ci if (err) 24462306a36Sopenharmony_ci goto error_free; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci clock->reg_base = pcim_iomap_table(pdev)[PCI_PTP_BAR_NO]; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci spin_lock_init(&clock->spin_lock); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci cc = &clock->cycle_counter; 25162306a36Sopenharmony_ci cc->read = cavium_ptp_cc_read; 25262306a36Sopenharmony_ci cc->mask = CYCLECOUNTER_MASK(64); 25362306a36Sopenharmony_ci cc->mult = 1; 25462306a36Sopenharmony_ci cc->shift = 0; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci timecounter_init(&clock->time_counter, &clock->cycle_counter, 25762306a36Sopenharmony_ci ktime_to_ns(ktime_get_real())); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci clock->clock_rate = ptp_cavium_clock_get(); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci clock->ptp_info = (struct ptp_clock_info) { 26262306a36Sopenharmony_ci .owner = THIS_MODULE, 26362306a36Sopenharmony_ci .name = "ThunderX PTP", 26462306a36Sopenharmony_ci .max_adj = 1000000000ull, 26562306a36Sopenharmony_ci .n_ext_ts = 0, 26662306a36Sopenharmony_ci .n_pins = 0, 26762306a36Sopenharmony_ci .pps = 0, 26862306a36Sopenharmony_ci .adjfine = cavium_ptp_adjfine, 26962306a36Sopenharmony_ci .adjtime = cavium_ptp_adjtime, 27062306a36Sopenharmony_ci .gettime64 = cavium_ptp_gettime, 27162306a36Sopenharmony_ci .settime64 = cavium_ptp_settime, 27262306a36Sopenharmony_ci .enable = cavium_ptp_enable, 27362306a36Sopenharmony_ci }; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci clock_cfg = readq(clock->reg_base + PTP_CLOCK_CFG); 27662306a36Sopenharmony_ci clock_cfg |= PTP_CLOCK_CFG_PTP_EN; 27762306a36Sopenharmony_ci writeq(clock_cfg, clock->reg_base + PTP_CLOCK_CFG); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci clock_comp = ((u64)1000000000ull << 32) / clock->clock_rate; 28062306a36Sopenharmony_ci writeq(clock_comp, clock->reg_base + PTP_CLOCK_COMP); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci clock->ptp_clock = ptp_clock_register(&clock->ptp_info, dev); 28362306a36Sopenharmony_ci if (IS_ERR(clock->ptp_clock)) { 28462306a36Sopenharmony_ci err = PTR_ERR(clock->ptp_clock); 28562306a36Sopenharmony_ci goto error_stop; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci pci_set_drvdata(pdev, clock); 28962306a36Sopenharmony_ci return 0; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_cierror_stop: 29262306a36Sopenharmony_ci clock_cfg = readq(clock->reg_base + PTP_CLOCK_CFG); 29362306a36Sopenharmony_ci clock_cfg &= ~PTP_CLOCK_CFG_PTP_EN; 29462306a36Sopenharmony_ci writeq(clock_cfg, clock->reg_base + PTP_CLOCK_CFG); 29562306a36Sopenharmony_ci pcim_iounmap_regions(pdev, 1 << PCI_PTP_BAR_NO); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cierror_free: 29862306a36Sopenharmony_ci devm_kfree(dev, clock); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cierror: 30162306a36Sopenharmony_ci /* For `cavium_ptp_get()` we need to differentiate between the case 30262306a36Sopenharmony_ci * when the core has not tried to probe this device and the case when 30362306a36Sopenharmony_ci * the probe failed. In the later case we pretend that the 30462306a36Sopenharmony_ci * initialization was successful and keep the error in 30562306a36Sopenharmony_ci * `dev->driver_data`. 30662306a36Sopenharmony_ci */ 30762306a36Sopenharmony_ci pci_set_drvdata(pdev, ERR_PTR(err)); 30862306a36Sopenharmony_ci return 0; 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cistatic void cavium_ptp_remove(struct pci_dev *pdev) 31262306a36Sopenharmony_ci{ 31362306a36Sopenharmony_ci struct cavium_ptp *clock = pci_get_drvdata(pdev); 31462306a36Sopenharmony_ci u64 clock_cfg; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (IS_ERR_OR_NULL(clock)) 31762306a36Sopenharmony_ci return; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci ptp_clock_unregister(clock->ptp_clock); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci clock_cfg = readq(clock->reg_base + PTP_CLOCK_CFG); 32262306a36Sopenharmony_ci clock_cfg &= ~PTP_CLOCK_CFG_PTP_EN; 32362306a36Sopenharmony_ci writeq(clock_cfg, clock->reg_base + PTP_CLOCK_CFG); 32462306a36Sopenharmony_ci} 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_cistatic const struct pci_device_id cavium_ptp_id_table[] = { 32762306a36Sopenharmony_ci { PCI_DEVICE_SUB(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_CAVIUM_PTP, 32862306a36Sopenharmony_ci PCI_VENDOR_ID_CAVIUM, PCI_SUBSYS_DEVID_88XX_PTP) }, 32962306a36Sopenharmony_ci { PCI_DEVICE_SUB(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_CAVIUM_PTP, 33062306a36Sopenharmony_ci PCI_VENDOR_ID_CAVIUM, PCI_SUBSYS_DEVID_81XX_PTP) }, 33162306a36Sopenharmony_ci { PCI_DEVICE_SUB(PCI_VENDOR_ID_CAVIUM, PCI_DEVICE_ID_CAVIUM_PTP, 33262306a36Sopenharmony_ci PCI_VENDOR_ID_CAVIUM, PCI_SUBSYS_DEVID_83XX_PTP) }, 33362306a36Sopenharmony_ci { 0, } 33462306a36Sopenharmony_ci}; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_cistatic struct pci_driver cavium_ptp_driver = { 33762306a36Sopenharmony_ci .name = DRV_NAME, 33862306a36Sopenharmony_ci .id_table = cavium_ptp_id_table, 33962306a36Sopenharmony_ci .probe = cavium_ptp_probe, 34062306a36Sopenharmony_ci .remove = cavium_ptp_remove, 34162306a36Sopenharmony_ci}; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_cimodule_pci_driver(cavium_ptp_driver); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ciMODULE_DESCRIPTION(DRV_NAME); 34662306a36Sopenharmony_ciMODULE_AUTHOR("Cavium Networks <support@cavium.com>"); 34762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 34862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, cavium_ptp_id_table); 349