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(&regs->systime_lo);
488c2ecf20Sopenharmony_ci	hi = __raw_readl(&regs->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, &regs->systime_lo);
668c2ecf20Sopenharmony_ci	__raw_writel(hi, &regs->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(&regs->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(&regs->asms_hi);
868c2ecf20Sopenharmony_ci			lo = __raw_readl(&regs->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(&regs->amms_hi);
1008c2ecf20Sopenharmony_ci			lo = __raw_readl(&regs->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, &regs->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, &regs->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(&register_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(&register_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(&register_lock, flags);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	ns = ixp_systime_read(regs);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&register_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(&register_lock, flags);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	ixp_systime_write(regs, ns);
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&register_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