411 lines
10 KiB
C
411 lines
10 KiB
C
/**
|
|
NVT ptp function
|
|
NVT ptp driver
|
|
@file ptp_nvt.c
|
|
@ingroup
|
|
@note
|
|
Copyright Novatek Microelectronics Corp. 2021. 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 as
|
|
published by the Free Software Foundation.
|
|
*/
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/ptp_clock_kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/clocksource.h>
|
|
#include <linux/time.h>
|
|
#include <linux/timecounter.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/irqreturn.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#define PTP_MAX_VALUE 0xFFFFFFFF
|
|
#define PTP_SHIFT 32ULL
|
|
#define PTP_MAX_HZ 3000000
|
|
#define PTP_MAX_DIV_BIT 255
|
|
#define PTP_TIMER_NUM (18)
|
|
#define PTP_TIMER_DIV_SRC 1
|
|
#if (PTP_TIMER_DIV_SRC == 0)
|
|
#define PTP_TIMER_DIV_DEF 0xFFFFFF00
|
|
#else
|
|
#define PTP_TIMER_DIV_DEF 0xFFFF00FF
|
|
#endif
|
|
#define PTP_TIMER_DIV_BASE 0x30
|
|
#define PTP_TIMER_DIV_VAL (1 << ( 16 + PTP_TIMER_DIV_SRC) )
|
|
#define PTP_TIMER_IRQ_ENABLE 0x00
|
|
#define PTP_TIMER_IRQ_STS 0x10
|
|
#define PTP_TIMER_IRQ_BASE 0x20
|
|
#define PTP_TIMER_IRQ_VAL (1 << PTP_TIMER_NUM)
|
|
#define PTP_TIMER_CTL_BASE (0x10 * PTP_TIMER_NUM + 0x100)
|
|
#define PTP_TIMER_TVAL_BASE (PTP_TIMER_CTL_BASE + 0x04)
|
|
#define PTP_TIMER_VALUE_BASE (PTP_TIMER_CTL_BASE + 0x08)
|
|
#define PTP_TIMER_CNT_BASE (PTP_TIMER_CTL_BASE + 0x0C)
|
|
#define PTP_TIMER_ENABLE 1
|
|
#define PTP_TIMER_DISABLE 0
|
|
#define PTP_TIMER_FREQ_SHIFT (PTP_TIMER_DIV_SRC * 8)
|
|
#define PTP_TIMER_UP (0x3 + 0x4 * PTP_TIMER_DIV_SRC)
|
|
/* ptp dte priv structure */
|
|
struct ptp_dte {
|
|
void __iomem *regs;
|
|
struct ptp_clock *ptp_clk;
|
|
struct ptp_clock_info caps;
|
|
struct device *dev;
|
|
spinlock_t lock;
|
|
|
|
struct cyclecounter cc;
|
|
struct timecounter tc;
|
|
struct clk *clk;
|
|
u32 freq;
|
|
};
|
|
|
|
struct ptp_dte *ptp_dte;
|
|
static int ptp_cyclecounter_init(struct ptp_dte *ptp);
|
|
static int ptp_dte_enable(struct ptp_clock_info *ptp,
|
|
struct ptp_clock_request *rq, int on);
|
|
|
|
#define MAX_TIMER_NUM 20
|
|
#ifdef CONFIG_NOVATEK_TIMER
|
|
extern void __iomem *nvttmr_base;
|
|
extern spinlock_t nvttmr_lock;
|
|
extern u32 nvttmr_in_use;
|
|
#else
|
|
void __iomem *nvttmr_base;
|
|
spinlock_t nvttmr_lock;
|
|
u32 nvttmr_in_use;
|
|
#endif
|
|
|
|
static s64 dte_read_nco(void __iomem *regs)
|
|
{
|
|
u32 sum1;
|
|
s64 ns;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&nvttmr_lock, flags);
|
|
|
|
sum1 = readl(regs + PTP_TIMER_VALUE_BASE);
|
|
|
|
ns = (s64)sum1;
|
|
|
|
spin_unlock_irqrestore(&nvttmr_lock, flags);
|
|
return ns;
|
|
}
|
|
|
|
static int ptp_dte_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
|
|
{
|
|
unsigned long flags;
|
|
struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps);
|
|
|
|
|
|
dev_err(ptp_dte->dev, "%s not support\n", __func__);
|
|
return -EINVAL;
|
|
|
|
if (abs(ppb) > ptp_dte->caps.max_adj || ppb < 0) {
|
|
dev_err(ptp_dte->dev, "ppb adj is wrong value\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ptp_dte_enable(&ptp_dte->caps ,0,PTP_TIMER_DISABLE);
|
|
|
|
spin_lock_irqsave(&ptp_dte->lock, flags);
|
|
ptp_dte->freq = ppb;
|
|
spin_unlock_irqrestore(&ptp_dte->lock, flags);
|
|
|
|
ptp_dte_enable(&ptp_dte->caps ,0,PTP_TIMER_ENABLE);
|
|
ptp_cyclecounter_init(ptp_dte);
|
|
return 0;
|
|
}
|
|
|
|
static int ptp_dte_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
|
{
|
|
unsigned long flags;
|
|
struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps);
|
|
|
|
spin_lock_irqsave(&ptp_dte->lock, flags);
|
|
|
|
timecounter_adjtime(&ptp_dte->tc, delta);
|
|
|
|
spin_unlock_irqrestore(&ptp_dte->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ptp_dte_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
|
|
{
|
|
unsigned long flags;
|
|
struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps);
|
|
|
|
spin_lock_irqsave(&ptp_dte->lock, flags);
|
|
*ts = ns_to_timespec64(timecounter_read(&ptp_dte->tc));
|
|
spin_unlock_irqrestore(&ptp_dte->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int ptp_dte_settime(struct ptp_clock_info *ptp,
|
|
const struct timespec64 *ts)
|
|
{
|
|
unsigned long flags;
|
|
struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps);
|
|
s64 ns, new_ns;
|
|
|
|
spin_lock_irqsave(&ptp_dte->lock, flags);
|
|
|
|
new_ns = timespec64_to_ns(ts);
|
|
ns = timecounter_read(&ptp_dte->tc);
|
|
new_ns -= ns;
|
|
timecounter_adjtime(&ptp_dte->tc, new_ns);
|
|
|
|
spin_unlock_irqrestore(&ptp_dte->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ptp_dte_enable(struct ptp_clock_info *ptp,
|
|
struct ptp_clock_request *rq, int on)
|
|
{
|
|
unsigned long flags;
|
|
struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps);
|
|
u32 val;
|
|
u8 freq = (PTP_MAX_HZ / ptp_dte->freq) - 1 ;
|
|
if ((PTP_MAX_HZ / ptp_dte->freq) > PTP_MAX_DIV_BIT) {
|
|
dev_err(ptp_dte->dev, "%s: freq too slow should > %d\n", __func__, PTP_MAX_HZ / PTP_MAX_DIV_BIT);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&nvttmr_lock, flags);
|
|
if (on == PTP_TIMER_ENABLE) {
|
|
|
|
val = readl(ptp_dte->regs + PTP_TIMER_IRQ_ENABLE);
|
|
val |= PTP_MAX_VALUE;
|
|
writel( val, (ptp_dte->regs + PTP_TIMER_IRQ_ENABLE));
|
|
|
|
val = readl(ptp_dte->regs + PTP_TIMER_IRQ_BASE);
|
|
val &= ~(PTP_TIMER_IRQ_VAL);
|
|
writel( val, (ptp_dte->regs + PTP_TIMER_IRQ_BASE));
|
|
|
|
val = readl(ptp_dte->regs + PTP_TIMER_DIV_BASE);
|
|
val &= PTP_TIMER_DIV_DEF;
|
|
val |= (freq << PTP_TIMER_FREQ_SHIFT) + PTP_TIMER_DIV_VAL;
|
|
writel( val, (ptp_dte->regs + PTP_TIMER_DIV_BASE));
|
|
|
|
val = readl(ptp_dte->regs + PTP_TIMER_TVAL_BASE);
|
|
val |= PTP_MAX_VALUE;
|
|
writel( val, (ptp_dte->regs + PTP_TIMER_TVAL_BASE));
|
|
|
|
val = readl(ptp_dte->regs + PTP_TIMER_CNT_BASE);
|
|
val &= ~(PTP_MAX_VALUE);
|
|
writel( val, (ptp_dte->regs + PTP_TIMER_CNT_BASE));
|
|
|
|
val = readl(ptp_dte->regs + PTP_TIMER_CTL_BASE);
|
|
val |= PTP_TIMER_UP;
|
|
writel( val, (ptp_dte->regs + PTP_TIMER_CTL_BASE));
|
|
|
|
} else if (on == PTP_TIMER_DISABLE) {
|
|
|
|
val = readl(ptp_dte->regs + PTP_TIMER_CTL_BASE);
|
|
val &= ~(PTP_TIMER_UP);
|
|
writel( val, (ptp_dte->regs + PTP_TIMER_CTL_BASE));
|
|
|
|
} else {
|
|
dev_err(ptp_dte->dev, "%s: on/off should be set 1 or 0\n", __func__);
|
|
}
|
|
spin_unlock_irqrestore(&nvttmr_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct ptp_clock_info ptp_dte_caps = {
|
|
.owner = THIS_MODULE,
|
|
.name = "DTE PTP timer",
|
|
.max_adj = 50000000,
|
|
.n_ext_ts = 0,
|
|
.n_pins = 0,
|
|
.pps = 0,
|
|
.adjfreq = ptp_dte_adjfreq,
|
|
.adjtime = ptp_dte_adjtime,
|
|
.gettime64 = ptp_dte_gettime,
|
|
.settime64 = ptp_dte_settime,
|
|
.enable = ptp_dte_enable,
|
|
};
|
|
|
|
static u64 ptp_cyclecounter_read(const struct cyclecounter *cc)
|
|
{
|
|
return (u64)(dte_read_nco(ptp_dte->regs));
|
|
}
|
|
|
|
static int ptp_cyclecounter_init(struct ptp_dte *ptp)
|
|
{
|
|
u64 start_count;
|
|
u32 mult, shift;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&ptp_dte->lock, flags);
|
|
|
|
|
|
start_count = (u64)ktime_to_ns(ktime_get_real());
|
|
clocks_calc_mult_shift(&mult,&shift,ptp->freq,NSEC_PER_SEC,60);
|
|
ptp->cc.read = ptp_cyclecounter_read;
|
|
ptp->cc.mask = CLOCKSOURCE_MASK(32);
|
|
ptp->cc.mult = mult;
|
|
ptp->cc.shift = shift;
|
|
|
|
timecounter_init(&ptp->tc,
|
|
&ptp->cc, start_count);
|
|
|
|
|
|
spin_unlock_irqrestore(&ptp_dte->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
/*static irqreturn_t nvt_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct clock_event_device *clkevt = (struct clock_event_device *)dev_id;
|
|
u32 val;
|
|
unsigned long flags;
|
|
int ret = IRQ_HANDLED;
|
|
|
|
spin_lock_irqsave(&nvttmr_lock, flags);
|
|
|
|
val = readl(ptp_dte->regs + PTP_TIMER_IRQ_STS);
|
|
|
|
if (val & (1 << PTP_TIMER_NUM)) {
|
|
val = (1 << PTP_TIMER_NUM);
|
|
writel( val, (ptp_dte->regs + PTP_TIMER_IRQ_STS));
|
|
}
|
|
else
|
|
ret = IRQ_NONE;
|
|
|
|
spin_unlock_irqrestore(&nvttmr_lock, flags);
|
|
|
|
return ret;
|
|
}*/
|
|
|
|
static int ptp_dte_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct resource *res;
|
|
struct device_node *node = pdev->dev.of_node;
|
|
u32 *pfreq;
|
|
int ret = 0;
|
|
|
|
if (MAX_TIMER_NUM < PTP_TIMER_NUM) {
|
|
dev_err(dev, "Timer num not support\n");
|
|
return -EINVAL;
|
|
}
|
|
if ((nvttmr_in_use & (1 << PTP_TIMER_NUM))) {
|
|
dev_err(dev, "Timer num %d already in use\n",PTP_TIMER_NUM);
|
|
return -EINVAL;
|
|
} else {
|
|
nvttmr_in_use |= (1 << PTP_TIMER_NUM);
|
|
}
|
|
ptp_dte = devm_kzalloc(dev, sizeof(struct ptp_dte), GFP_KERNEL);
|
|
if (!ptp_dte)
|
|
goto kzalloc_err;
|
|
|
|
#ifdef CONFIG_NOVATEK_TIMER
|
|
ptp_dte->regs = nvttmr_base;
|
|
#else
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
ptp_dte->regs = devm_ioremap_resource(dev, res);
|
|
#endif
|
|
if (IS_ERR(ptp_dte->regs)) {
|
|
dev_err(dev, "%s: io remap failed\n", __func__);
|
|
goto remap_err;
|
|
}
|
|
|
|
#ifndef CONFIG_NOVATEK_TIMER
|
|
spin_lock_init(&nvttmr_lock);
|
|
#endif
|
|
spin_lock_init(&ptp_dte->lock);
|
|
|
|
ptp_dte->clk = clk_get(dev, "f0040000.timer");
|
|
if (IS_ERR(ptp_dte->clk)) {
|
|
dev_err(dev, "%s:%d clk_get fail\n", __func__, __LINE__);
|
|
goto clk_err;
|
|
}
|
|
if (clk_prepare_enable(ptp_dte->clk) != 0) {
|
|
dev_err(dev, "%s:%d enable clk fail\n", __func__, __LINE__);
|
|
goto clk_err;
|
|
}
|
|
ptp_dte->freq = (u32)(clk_get_rate(ptp_dte->clk));
|
|
|
|
pfreq = (u32 *)of_get_property(node, "clock-frequency", NULL);
|
|
if (pfreq != NULL) {
|
|
ptp_dte->freq = __be32_to_cpu(*pfreq);
|
|
}
|
|
ptp_dte_enable(&ptp_dte->caps ,0,1);
|
|
ptp_cyclecounter_init(ptp_dte);
|
|
|
|
ptp_dte->dev = dev;
|
|
ptp_dte->caps = ptp_dte_caps;
|
|
|
|
/* int irq_num = platform_get_irq(pdev, 0);
|
|
if (request_irq(irq_num, nvt_interrupt, IRQF_SHARED, "TIMER_TEST", ptp_dte->dev))
|
|
{
|
|
printk("%s:%d request_irq fail %d\n", __func__, __LINE__, irq_num);
|
|
return -1;
|
|
}*/
|
|
return 0;
|
|
clk_err:
|
|
#ifndef CONFIG_NOVATEK_TIMER
|
|
iounmap(ptp_dte->regs);
|
|
#endif
|
|
remap_err:
|
|
kfree(ptp_dte);
|
|
kzalloc_err:
|
|
nvttmr_in_use &= ~(1 << PTP_TIMER_NUM);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ptp_dte_remove(struct platform_device *pdev)
|
|
{
|
|
nvttmr_in_use &= ~(1 << PTP_TIMER_NUM);
|
|
|
|
if (ptp_dte->regs)
|
|
ptp_dte_enable(&ptp_dte->caps , 0, PTP_TIMER_DISABLE);
|
|
|
|
if (ptp_dte->clk) {
|
|
clk_disable(ptp_dte->clk);
|
|
}
|
|
//free_irq(irq_num, ptp_dte->dev);
|
|
#ifndef CONFIG_NOVATEK_TIMER
|
|
if (ptp_dte->regs) {
|
|
iounmap(ptp_dte->regs);
|
|
}
|
|
#endif
|
|
if (ptp_dte) {
|
|
kfree(ptp_dte);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id ptp_dte_of_match[] = {
|
|
{ .compatible = "nvt,ptp-dte", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ptp_dte_of_match);
|
|
|
|
static struct platform_driver ptp_dte_driver = {
|
|
.driver = {
|
|
.name = "ptp-dte",
|
|
.pm = NULL,
|
|
.of_match_table = ptp_dte_of_match,
|
|
},
|
|
.probe = ptp_dte_probe,
|
|
.remove = ptp_dte_remove,
|
|
};
|
|
module_platform_driver(ptp_dte_driver);
|
|
|
|
MODULE_AUTHOR("Novatek");
|
|
MODULE_VERSION("1.0.0");
|
|
MODULE_DESCRIPTION("Novatek PTP Clock driver");
|
|
MODULE_LICENSE("GPL v2");
|