18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * PTP 1588 clock using the IXP46X 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2010 OMICRON electronics GmbH 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci#include <linux/device.h> 88c2ecf20Sopenharmony_ci#include <linux/err.h> 98c2ecf20Sopenharmony_ci#include <linux/gpio.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/io.h> 138c2ecf20Sopenharmony_ci#include <linux/irq.h> 148c2ecf20Sopenharmony_ci#include <linux/kernel.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#include <linux/ptp_clock_kernel.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include "ixp46x_ts.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define DRIVER "ptp_ixp46x" 228c2ecf20Sopenharmony_ci#define N_EXT_TS 2 238c2ecf20Sopenharmony_ci#define MASTER_GPIO 8 248c2ecf20Sopenharmony_ci#define MASTER_IRQ 25 258c2ecf20Sopenharmony_ci#define SLAVE_GPIO 7 268c2ecf20Sopenharmony_ci#define SLAVE_IRQ 24 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistruct ixp_clock { 298c2ecf20Sopenharmony_ci struct ixp46x_ts_regs *regs; 308c2ecf20Sopenharmony_ci struct ptp_clock *ptp_clock; 318c2ecf20Sopenharmony_ci struct ptp_clock_info caps; 328c2ecf20Sopenharmony_ci int exts0_enabled; 338c2ecf20Sopenharmony_ci int exts1_enabled; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ciDEFINE_SPINLOCK(register_lock); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* 398c2ecf20Sopenharmony_ci * Register access functions 408c2ecf20Sopenharmony_ci */ 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic u64 ixp_systime_read(struct ixp46x_ts_regs *regs) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci u64 ns; 458c2ecf20Sopenharmony_ci u32 lo, hi; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci lo = __raw_readl(®s->systime_lo); 488c2ecf20Sopenharmony_ci hi = __raw_readl(®s->systime_hi); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci ns = ((u64) hi) << 32; 518c2ecf20Sopenharmony_ci ns |= lo; 528c2ecf20Sopenharmony_ci ns <<= TICKS_NS_SHIFT; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci return ns; 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic void ixp_systime_write(struct ixp46x_ts_regs *regs, u64 ns) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci u32 hi, lo; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci ns >>= TICKS_NS_SHIFT; 628c2ecf20Sopenharmony_ci hi = ns >> 32; 638c2ecf20Sopenharmony_ci lo = ns & 0xffffffff; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci __raw_writel(lo, ®s->systime_lo); 668c2ecf20Sopenharmony_ci __raw_writel(hi, ®s->systime_hi); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci/* 708c2ecf20Sopenharmony_ci * Interrupt service routine 718c2ecf20Sopenharmony_ci */ 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic irqreturn_t isr(int irq, void *priv) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci struct ixp_clock *ixp_clock = priv; 768c2ecf20Sopenharmony_ci struct ixp46x_ts_regs *regs = ixp_clock->regs; 778c2ecf20Sopenharmony_ci struct ptp_clock_event event; 788c2ecf20Sopenharmony_ci u32 ack = 0, lo, hi, val; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci val = __raw_readl(®s->event); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci if (val & TSER_SNS) { 838c2ecf20Sopenharmony_ci ack |= TSER_SNS; 848c2ecf20Sopenharmony_ci if (ixp_clock->exts0_enabled) { 858c2ecf20Sopenharmony_ci hi = __raw_readl(®s->asms_hi); 868c2ecf20Sopenharmony_ci lo = __raw_readl(®s->asms_lo); 878c2ecf20Sopenharmony_ci event.type = PTP_CLOCK_EXTTS; 888c2ecf20Sopenharmony_ci event.index = 0; 898c2ecf20Sopenharmony_ci event.timestamp = ((u64) hi) << 32; 908c2ecf20Sopenharmony_ci event.timestamp |= lo; 918c2ecf20Sopenharmony_ci event.timestamp <<= TICKS_NS_SHIFT; 928c2ecf20Sopenharmony_ci ptp_clock_event(ixp_clock->ptp_clock, &event); 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci } 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci if (val & TSER_SNM) { 978c2ecf20Sopenharmony_ci ack |= TSER_SNM; 988c2ecf20Sopenharmony_ci if (ixp_clock->exts1_enabled) { 998c2ecf20Sopenharmony_ci hi = __raw_readl(®s->amms_hi); 1008c2ecf20Sopenharmony_ci lo = __raw_readl(®s->amms_lo); 1018c2ecf20Sopenharmony_ci event.type = PTP_CLOCK_EXTTS; 1028c2ecf20Sopenharmony_ci event.index = 1; 1038c2ecf20Sopenharmony_ci event.timestamp = ((u64) hi) << 32; 1048c2ecf20Sopenharmony_ci event.timestamp |= lo; 1058c2ecf20Sopenharmony_ci event.timestamp <<= TICKS_NS_SHIFT; 1068c2ecf20Sopenharmony_ci ptp_clock_event(ixp_clock->ptp_clock, &event); 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (val & TTIPEND) 1118c2ecf20Sopenharmony_ci ack |= TTIPEND; /* this bit seems to be always set */ 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci if (ack) { 1148c2ecf20Sopenharmony_ci __raw_writel(ack, ®s->event); 1158c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1168c2ecf20Sopenharmony_ci } else 1178c2ecf20Sopenharmony_ci return IRQ_NONE; 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci/* 1218c2ecf20Sopenharmony_ci * PTP clock operations 1228c2ecf20Sopenharmony_ci */ 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic int ptp_ixp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci u64 adj; 1278c2ecf20Sopenharmony_ci u32 diff, addend; 1288c2ecf20Sopenharmony_ci int neg_adj = 0; 1298c2ecf20Sopenharmony_ci struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 1308c2ecf20Sopenharmony_ci struct ixp46x_ts_regs *regs = ixp_clock->regs; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci if (ppb < 0) { 1338c2ecf20Sopenharmony_ci neg_adj = 1; 1348c2ecf20Sopenharmony_ci ppb = -ppb; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci addend = DEFAULT_ADDEND; 1378c2ecf20Sopenharmony_ci adj = addend; 1388c2ecf20Sopenharmony_ci adj *= ppb; 1398c2ecf20Sopenharmony_ci diff = div_u64(adj, 1000000000ULL); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci addend = neg_adj ? addend - diff : addend + diff; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci __raw_writel(addend, ®s->addend); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci return 0; 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic int ptp_ixp_adjtime(struct ptp_clock_info *ptp, s64 delta) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci s64 now; 1518c2ecf20Sopenharmony_ci unsigned long flags; 1528c2ecf20Sopenharmony_ci struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 1538c2ecf20Sopenharmony_ci struct ixp46x_ts_regs *regs = ixp_clock->regs; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci spin_lock_irqsave(®ister_lock, flags); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci now = ixp_systime_read(regs); 1588c2ecf20Sopenharmony_ci now += delta; 1598c2ecf20Sopenharmony_ci ixp_systime_write(regs, now); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci spin_unlock_irqrestore(®ister_lock, flags); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci return 0; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic int ptp_ixp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci u64 ns; 1698c2ecf20Sopenharmony_ci unsigned long flags; 1708c2ecf20Sopenharmony_ci struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 1718c2ecf20Sopenharmony_ci struct ixp46x_ts_regs *regs = ixp_clock->regs; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci spin_lock_irqsave(®ister_lock, flags); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci ns = ixp_systime_read(regs); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci spin_unlock_irqrestore(®ister_lock, flags); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci *ts = ns_to_timespec64(ns); 1808c2ecf20Sopenharmony_ci return 0; 1818c2ecf20Sopenharmony_ci} 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic int ptp_ixp_settime(struct ptp_clock_info *ptp, 1848c2ecf20Sopenharmony_ci const struct timespec64 *ts) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci u64 ns; 1878c2ecf20Sopenharmony_ci unsigned long flags; 1888c2ecf20Sopenharmony_ci struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 1898c2ecf20Sopenharmony_ci struct ixp46x_ts_regs *regs = ixp_clock->regs; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci ns = timespec64_to_ns(ts); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci spin_lock_irqsave(®ister_lock, flags); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci ixp_systime_write(regs, ns); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci spin_unlock_irqrestore(®ister_lock, flags); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci return 0; 2008c2ecf20Sopenharmony_ci} 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int ptp_ixp_enable(struct ptp_clock_info *ptp, 2038c2ecf20Sopenharmony_ci struct ptp_clock_request *rq, int on) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci switch (rq->type) { 2088c2ecf20Sopenharmony_ci case PTP_CLK_REQ_EXTTS: 2098c2ecf20Sopenharmony_ci switch (rq->extts.index) { 2108c2ecf20Sopenharmony_ci case 0: 2118c2ecf20Sopenharmony_ci ixp_clock->exts0_enabled = on ? 1 : 0; 2128c2ecf20Sopenharmony_ci break; 2138c2ecf20Sopenharmony_ci case 1: 2148c2ecf20Sopenharmony_ci ixp_clock->exts1_enabled = on ? 1 : 0; 2158c2ecf20Sopenharmony_ci break; 2168c2ecf20Sopenharmony_ci default: 2178c2ecf20Sopenharmony_ci return -EINVAL; 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci return 0; 2208c2ecf20Sopenharmony_ci default: 2218c2ecf20Sopenharmony_ci break; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 2258c2ecf20Sopenharmony_ci} 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_cistatic const struct ptp_clock_info ptp_ixp_caps = { 2288c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2298c2ecf20Sopenharmony_ci .name = "IXP46X timer", 2308c2ecf20Sopenharmony_ci .max_adj = 66666655, 2318c2ecf20Sopenharmony_ci .n_ext_ts = N_EXT_TS, 2328c2ecf20Sopenharmony_ci .n_pins = 0, 2338c2ecf20Sopenharmony_ci .pps = 0, 2348c2ecf20Sopenharmony_ci .adjfreq = ptp_ixp_adjfreq, 2358c2ecf20Sopenharmony_ci .adjtime = ptp_ixp_adjtime, 2368c2ecf20Sopenharmony_ci .gettime64 = ptp_ixp_gettime, 2378c2ecf20Sopenharmony_ci .settime64 = ptp_ixp_settime, 2388c2ecf20Sopenharmony_ci .enable = ptp_ixp_enable, 2398c2ecf20Sopenharmony_ci}; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci/* module operations */ 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cistatic struct ixp_clock ixp_clock; 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_cistatic int setup_interrupt(int gpio) 2468c2ecf20Sopenharmony_ci{ 2478c2ecf20Sopenharmony_ci int irq; 2488c2ecf20Sopenharmony_ci int err; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci err = gpio_request(gpio, "ixp4-ptp"); 2518c2ecf20Sopenharmony_ci if (err) 2528c2ecf20Sopenharmony_ci return err; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci err = gpio_direction_input(gpio); 2558c2ecf20Sopenharmony_ci if (err) 2568c2ecf20Sopenharmony_ci return err; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci irq = gpio_to_irq(gpio); 2598c2ecf20Sopenharmony_ci if (irq < 0) 2608c2ecf20Sopenharmony_ci return irq; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci err = irq_set_irq_type(irq, IRQF_TRIGGER_FALLING); 2638c2ecf20Sopenharmony_ci if (err) { 2648c2ecf20Sopenharmony_ci pr_err("cannot set trigger type for irq %d\n", irq); 2658c2ecf20Sopenharmony_ci return err; 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci err = request_irq(irq, isr, 0, DRIVER, &ixp_clock); 2698c2ecf20Sopenharmony_ci if (err) { 2708c2ecf20Sopenharmony_ci pr_err("request_irq failed for irq %d\n", irq); 2718c2ecf20Sopenharmony_ci return err; 2728c2ecf20Sopenharmony_ci } 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci return irq; 2758c2ecf20Sopenharmony_ci} 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_cistatic void __exit ptp_ixp_exit(void) 2788c2ecf20Sopenharmony_ci{ 2798c2ecf20Sopenharmony_ci free_irq(MASTER_IRQ, &ixp_clock); 2808c2ecf20Sopenharmony_ci free_irq(SLAVE_IRQ, &ixp_clock); 2818c2ecf20Sopenharmony_ci ixp46x_phc_index = -1; 2828c2ecf20Sopenharmony_ci ptp_clock_unregister(ixp_clock.ptp_clock); 2838c2ecf20Sopenharmony_ci} 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_cistatic int __init ptp_ixp_init(void) 2868c2ecf20Sopenharmony_ci{ 2878c2ecf20Sopenharmony_ci if (!cpu_is_ixp46x()) 2888c2ecf20Sopenharmony_ci return -ENODEV; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci ixp_clock.regs = 2918c2ecf20Sopenharmony_ci (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci ixp_clock.caps = ptp_ixp_caps; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci ixp_clock.ptp_clock = ptp_clock_register(&ixp_clock.caps, NULL); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci if (IS_ERR(ixp_clock.ptp_clock)) 2988c2ecf20Sopenharmony_ci return PTR_ERR(ixp_clock.ptp_clock); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci ixp46x_phc_index = ptp_clock_index(ixp_clock.ptp_clock); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci __raw_writel(DEFAULT_ADDEND, &ixp_clock.regs->addend); 3038c2ecf20Sopenharmony_ci __raw_writel(1, &ixp_clock.regs->trgt_lo); 3048c2ecf20Sopenharmony_ci __raw_writel(0, &ixp_clock.regs->trgt_hi); 3058c2ecf20Sopenharmony_ci __raw_writel(TTIPEND, &ixp_clock.regs->event); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci if (MASTER_IRQ != setup_interrupt(MASTER_GPIO)) { 3088c2ecf20Sopenharmony_ci pr_err("failed to setup gpio %d as irq\n", MASTER_GPIO); 3098c2ecf20Sopenharmony_ci goto no_master; 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci if (SLAVE_IRQ != setup_interrupt(SLAVE_GPIO)) { 3128c2ecf20Sopenharmony_ci pr_err("failed to setup gpio %d as irq\n", SLAVE_GPIO); 3138c2ecf20Sopenharmony_ci goto no_slave; 3148c2ecf20Sopenharmony_ci } 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_ci return 0; 3178c2ecf20Sopenharmony_cino_slave: 3188c2ecf20Sopenharmony_ci free_irq(MASTER_IRQ, &ixp_clock); 3198c2ecf20Sopenharmony_cino_master: 3208c2ecf20Sopenharmony_ci ptp_clock_unregister(ixp_clock.ptp_clock); 3218c2ecf20Sopenharmony_ci return -ENODEV; 3228c2ecf20Sopenharmony_ci} 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_cimodule_init(ptp_ixp_init); 3258c2ecf20Sopenharmony_cimodule_exit(ptp_ixp_exit); 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_ciMODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>"); 3288c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PTP clock using the IXP46X timer"); 3298c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 330