![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 675 mass ave cambridge ma 02139 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 441 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Michael Ellerman <mpe@ellerman.id.au> (powerpc) Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190520071858.739733335@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
648 lines
16 KiB
C
648 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* PTP 1588 clock for Freescale QorIQ 1588 timer
|
|
*
|
|
* Copyright (C) 2010 OMICRON electronics GmbH
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/timex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/fsl/ptp_qoriq.h>
|
|
|
|
/*
|
|
* Register access functions
|
|
*/
|
|
|
|
/* Caller must hold ptp_qoriq->lock. */
|
|
static u64 tmr_cnt_read(struct ptp_qoriq *ptp_qoriq)
|
|
{
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
u64 ns;
|
|
u32 lo, hi;
|
|
|
|
lo = ptp_qoriq->read(®s->ctrl_regs->tmr_cnt_l);
|
|
hi = ptp_qoriq->read(®s->ctrl_regs->tmr_cnt_h);
|
|
ns = ((u64) hi) << 32;
|
|
ns |= lo;
|
|
return ns;
|
|
}
|
|
|
|
/* Caller must hold ptp_qoriq->lock. */
|
|
static void tmr_cnt_write(struct ptp_qoriq *ptp_qoriq, u64 ns)
|
|
{
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
u32 hi = ns >> 32;
|
|
u32 lo = ns & 0xffffffff;
|
|
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_cnt_l, lo);
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_cnt_h, hi);
|
|
}
|
|
|
|
/* Caller must hold ptp_qoriq->lock. */
|
|
static void set_alarm(struct ptp_qoriq *ptp_qoriq)
|
|
{
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
u64 ns;
|
|
u32 lo, hi;
|
|
|
|
ns = tmr_cnt_read(ptp_qoriq) + 1500000000ULL;
|
|
ns = div_u64(ns, 1000000000UL) * 1000000000ULL;
|
|
ns -= ptp_qoriq->tclk_period;
|
|
hi = ns >> 32;
|
|
lo = ns & 0xffffffff;
|
|
ptp_qoriq->write(®s->alarm_regs->tmr_alarm1_l, lo);
|
|
ptp_qoriq->write(®s->alarm_regs->tmr_alarm1_h, hi);
|
|
}
|
|
|
|
/* Caller must hold ptp_qoriq->lock. */
|
|
static void set_fipers(struct ptp_qoriq *ptp_qoriq)
|
|
{
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
|
|
set_alarm(ptp_qoriq);
|
|
ptp_qoriq->write(®s->fiper_regs->tmr_fiper1, ptp_qoriq->tmr_fiper1);
|
|
ptp_qoriq->write(®s->fiper_regs->tmr_fiper2, ptp_qoriq->tmr_fiper2);
|
|
}
|
|
|
|
static int extts_clean_up(struct ptp_qoriq *ptp_qoriq, int index,
|
|
bool update_event)
|
|
{
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
struct ptp_clock_event event;
|
|
void __iomem *reg_etts_l;
|
|
void __iomem *reg_etts_h;
|
|
u32 valid, stat, lo, hi;
|
|
|
|
switch (index) {
|
|
case 0:
|
|
valid = ETS1_VLD;
|
|
reg_etts_l = ®s->etts_regs->tmr_etts1_l;
|
|
reg_etts_h = ®s->etts_regs->tmr_etts1_h;
|
|
break;
|
|
case 1:
|
|
valid = ETS2_VLD;
|
|
reg_etts_l = ®s->etts_regs->tmr_etts2_l;
|
|
reg_etts_h = ®s->etts_regs->tmr_etts2_h;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
event.type = PTP_CLOCK_EXTTS;
|
|
event.index = index;
|
|
|
|
do {
|
|
lo = ptp_qoriq->read(reg_etts_l);
|
|
hi = ptp_qoriq->read(reg_etts_h);
|
|
|
|
if (update_event) {
|
|
event.timestamp = ((u64) hi) << 32;
|
|
event.timestamp |= lo;
|
|
ptp_clock_event(ptp_qoriq->clock, &event);
|
|
}
|
|
|
|
stat = ptp_qoriq->read(®s->ctrl_regs->tmr_stat);
|
|
} while (ptp_qoriq->extts_fifo_support && (stat & valid));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Interrupt service routine
|
|
*/
|
|
|
|
irqreturn_t ptp_qoriq_isr(int irq, void *priv)
|
|
{
|
|
struct ptp_qoriq *ptp_qoriq = priv;
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
struct ptp_clock_event event;
|
|
u64 ns;
|
|
u32 ack = 0, lo, hi, mask, val, irqs;
|
|
|
|
spin_lock(&ptp_qoriq->lock);
|
|
|
|
val = ptp_qoriq->read(®s->ctrl_regs->tmr_tevent);
|
|
mask = ptp_qoriq->read(®s->ctrl_regs->tmr_temask);
|
|
|
|
spin_unlock(&ptp_qoriq->lock);
|
|
|
|
irqs = val & mask;
|
|
|
|
if (irqs & ETS1) {
|
|
ack |= ETS1;
|
|
extts_clean_up(ptp_qoriq, 0, true);
|
|
}
|
|
|
|
if (irqs & ETS2) {
|
|
ack |= ETS2;
|
|
extts_clean_up(ptp_qoriq, 1, true);
|
|
}
|
|
|
|
if (irqs & ALM2) {
|
|
ack |= ALM2;
|
|
if (ptp_qoriq->alarm_value) {
|
|
event.type = PTP_CLOCK_ALARM;
|
|
event.index = 0;
|
|
event.timestamp = ptp_qoriq->alarm_value;
|
|
ptp_clock_event(ptp_qoriq->clock, &event);
|
|
}
|
|
if (ptp_qoriq->alarm_interval) {
|
|
ns = ptp_qoriq->alarm_value + ptp_qoriq->alarm_interval;
|
|
hi = ns >> 32;
|
|
lo = ns & 0xffffffff;
|
|
ptp_qoriq->write(®s->alarm_regs->tmr_alarm2_l, lo);
|
|
ptp_qoriq->write(®s->alarm_regs->tmr_alarm2_h, hi);
|
|
ptp_qoriq->alarm_value = ns;
|
|
} else {
|
|
spin_lock(&ptp_qoriq->lock);
|
|
mask = ptp_qoriq->read(®s->ctrl_regs->tmr_temask);
|
|
mask &= ~ALM2EN;
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_temask, mask);
|
|
spin_unlock(&ptp_qoriq->lock);
|
|
ptp_qoriq->alarm_value = 0;
|
|
ptp_qoriq->alarm_interval = 0;
|
|
}
|
|
}
|
|
|
|
if (irqs & PP1) {
|
|
ack |= PP1;
|
|
event.type = PTP_CLOCK_PPS;
|
|
ptp_clock_event(ptp_qoriq->clock, &event);
|
|
}
|
|
|
|
if (ack) {
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_tevent, ack);
|
|
return IRQ_HANDLED;
|
|
} else
|
|
return IRQ_NONE;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ptp_qoriq_isr);
|
|
|
|
/*
|
|
* PTP clock operations
|
|
*/
|
|
|
|
int ptp_qoriq_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
|
|
{
|
|
u64 adj, diff;
|
|
u32 tmr_add;
|
|
int neg_adj = 0;
|
|
struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps);
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
|
|
if (scaled_ppm < 0) {
|
|
neg_adj = 1;
|
|
scaled_ppm = -scaled_ppm;
|
|
}
|
|
tmr_add = ptp_qoriq->tmr_add;
|
|
adj = tmr_add;
|
|
|
|
/* calculate diff as adj*(scaled_ppm/65536)/1000000
|
|
* and round() to the nearest integer
|
|
*/
|
|
adj *= scaled_ppm;
|
|
diff = div_u64(adj, 8000000);
|
|
diff = (diff >> 13) + ((diff >> 12) & 1);
|
|
|
|
tmr_add = neg_adj ? tmr_add - diff : tmr_add + diff;
|
|
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_add, tmr_add);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ptp_qoriq_adjfine);
|
|
|
|
int ptp_qoriq_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
|
{
|
|
s64 now;
|
|
unsigned long flags;
|
|
struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps);
|
|
|
|
spin_lock_irqsave(&ptp_qoriq->lock, flags);
|
|
|
|
now = tmr_cnt_read(ptp_qoriq);
|
|
now += delta;
|
|
tmr_cnt_write(ptp_qoriq, now);
|
|
set_fipers(ptp_qoriq);
|
|
|
|
spin_unlock_irqrestore(&ptp_qoriq->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ptp_qoriq_adjtime);
|
|
|
|
int ptp_qoriq_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
|
|
{
|
|
u64 ns;
|
|
unsigned long flags;
|
|
struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps);
|
|
|
|
spin_lock_irqsave(&ptp_qoriq->lock, flags);
|
|
|
|
ns = tmr_cnt_read(ptp_qoriq);
|
|
|
|
spin_unlock_irqrestore(&ptp_qoriq->lock, flags);
|
|
|
|
*ts = ns_to_timespec64(ns);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ptp_qoriq_gettime);
|
|
|
|
int ptp_qoriq_settime(struct ptp_clock_info *ptp,
|
|
const struct timespec64 *ts)
|
|
{
|
|
u64 ns;
|
|
unsigned long flags;
|
|
struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps);
|
|
|
|
ns = timespec64_to_ns(ts);
|
|
|
|
spin_lock_irqsave(&ptp_qoriq->lock, flags);
|
|
|
|
tmr_cnt_write(ptp_qoriq, ns);
|
|
set_fipers(ptp_qoriq);
|
|
|
|
spin_unlock_irqrestore(&ptp_qoriq->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ptp_qoriq_settime);
|
|
|
|
int ptp_qoriq_enable(struct ptp_clock_info *ptp,
|
|
struct ptp_clock_request *rq, int on)
|
|
{
|
|
struct ptp_qoriq *ptp_qoriq = container_of(ptp, struct ptp_qoriq, caps);
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
unsigned long flags;
|
|
u32 bit, mask = 0;
|
|
|
|
switch (rq->type) {
|
|
case PTP_CLK_REQ_EXTTS:
|
|
switch (rq->extts.index) {
|
|
case 0:
|
|
bit = ETS1EN;
|
|
break;
|
|
case 1:
|
|
bit = ETS2EN;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (on)
|
|
extts_clean_up(ptp_qoriq, rq->extts.index, false);
|
|
|
|
break;
|
|
case PTP_CLK_REQ_PPS:
|
|
bit = PP1EN;
|
|
break;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
spin_lock_irqsave(&ptp_qoriq->lock, flags);
|
|
|
|
mask = ptp_qoriq->read(®s->ctrl_regs->tmr_temask);
|
|
if (on) {
|
|
mask |= bit;
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_tevent, bit);
|
|
} else {
|
|
mask &= ~bit;
|
|
}
|
|
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_temask, mask);
|
|
|
|
spin_unlock_irqrestore(&ptp_qoriq->lock, flags);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ptp_qoriq_enable);
|
|
|
|
static const struct ptp_clock_info ptp_qoriq_caps = {
|
|
.owner = THIS_MODULE,
|
|
.name = "qoriq ptp clock",
|
|
.max_adj = 512000,
|
|
.n_alarm = 0,
|
|
.n_ext_ts = N_EXT_TS,
|
|
.n_per_out = 0,
|
|
.n_pins = 0,
|
|
.pps = 1,
|
|
.adjfine = ptp_qoriq_adjfine,
|
|
.adjtime = ptp_qoriq_adjtime,
|
|
.gettime64 = ptp_qoriq_gettime,
|
|
.settime64 = ptp_qoriq_settime,
|
|
.enable = ptp_qoriq_enable,
|
|
};
|
|
|
|
/**
|
|
* ptp_qoriq_nominal_freq - calculate nominal frequency according to
|
|
* reference clock frequency
|
|
*
|
|
* @clk_src: reference clock frequency
|
|
*
|
|
* The nominal frequency is the desired clock frequency.
|
|
* It should be less than the reference clock frequency.
|
|
* It should be a factor of 1000MHz.
|
|
*
|
|
* Return the nominal frequency
|
|
*/
|
|
static u32 ptp_qoriq_nominal_freq(u32 clk_src)
|
|
{
|
|
u32 remainder = 0;
|
|
|
|
clk_src /= 1000000;
|
|
remainder = clk_src % 100;
|
|
if (remainder) {
|
|
clk_src -= remainder;
|
|
clk_src += 100;
|
|
}
|
|
|
|
do {
|
|
clk_src -= 100;
|
|
|
|
} while (1000 % clk_src);
|
|
|
|
return clk_src * 1000000;
|
|
}
|
|
|
|
/**
|
|
* ptp_qoriq_auto_config - calculate a set of default configurations
|
|
*
|
|
* @ptp_qoriq: pointer to ptp_qoriq
|
|
* @node: pointer to device_node
|
|
*
|
|
* If below dts properties are not provided, this function will be
|
|
* called to calculate a set of default configurations for them.
|
|
* "fsl,tclk-period"
|
|
* "fsl,tmr-prsc"
|
|
* "fsl,tmr-add"
|
|
* "fsl,tmr-fiper1"
|
|
* "fsl,tmr-fiper2"
|
|
* "fsl,max-adj"
|
|
*
|
|
* Return 0 if success
|
|
*/
|
|
static int ptp_qoriq_auto_config(struct ptp_qoriq *ptp_qoriq,
|
|
struct device_node *node)
|
|
{
|
|
struct clk *clk;
|
|
u64 freq_comp;
|
|
u64 max_adj;
|
|
u32 nominal_freq;
|
|
u32 remainder = 0;
|
|
u32 clk_src = 0;
|
|
|
|
ptp_qoriq->cksel = DEFAULT_CKSEL;
|
|
|
|
clk = of_clk_get(node, 0);
|
|
if (!IS_ERR(clk)) {
|
|
clk_src = clk_get_rate(clk);
|
|
clk_put(clk);
|
|
}
|
|
|
|
if (clk_src <= 100000000UL) {
|
|
pr_err("error reference clock value, or lower than 100MHz\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nominal_freq = ptp_qoriq_nominal_freq(clk_src);
|
|
if (!nominal_freq)
|
|
return -EINVAL;
|
|
|
|
ptp_qoriq->tclk_period = 1000000000UL / nominal_freq;
|
|
ptp_qoriq->tmr_prsc = DEFAULT_TMR_PRSC;
|
|
|
|
/* Calculate initial frequency compensation value for TMR_ADD register.
|
|
* freq_comp = ceil(2^32 / freq_ratio)
|
|
* freq_ratio = reference_clock_freq / nominal_freq
|
|
*/
|
|
freq_comp = ((u64)1 << 32) * nominal_freq;
|
|
freq_comp = div_u64_rem(freq_comp, clk_src, &remainder);
|
|
if (remainder)
|
|
freq_comp++;
|
|
|
|
ptp_qoriq->tmr_add = freq_comp;
|
|
ptp_qoriq->tmr_fiper1 = DEFAULT_FIPER1_PERIOD - ptp_qoriq->tclk_period;
|
|
ptp_qoriq->tmr_fiper2 = DEFAULT_FIPER2_PERIOD - ptp_qoriq->tclk_period;
|
|
|
|
/* max_adj = 1000000000 * (freq_ratio - 1.0) - 1
|
|
* freq_ratio = reference_clock_freq / nominal_freq
|
|
*/
|
|
max_adj = 1000000000ULL * (clk_src - nominal_freq);
|
|
max_adj = div_u64(max_adj, nominal_freq) - 1;
|
|
ptp_qoriq->caps.max_adj = max_adj;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ptp_qoriq_init(struct ptp_qoriq *ptp_qoriq, void __iomem *base,
|
|
const struct ptp_clock_info *caps)
|
|
{
|
|
struct device_node *node = ptp_qoriq->dev->of_node;
|
|
struct ptp_qoriq_registers *regs;
|
|
struct timespec64 now;
|
|
unsigned long flags;
|
|
u32 tmr_ctrl;
|
|
|
|
if (!node)
|
|
return -ENODEV;
|
|
|
|
ptp_qoriq->base = base;
|
|
ptp_qoriq->caps = *caps;
|
|
|
|
if (of_property_read_u32(node, "fsl,cksel", &ptp_qoriq->cksel))
|
|
ptp_qoriq->cksel = DEFAULT_CKSEL;
|
|
|
|
if (of_property_read_bool(node, "fsl,extts-fifo"))
|
|
ptp_qoriq->extts_fifo_support = true;
|
|
else
|
|
ptp_qoriq->extts_fifo_support = false;
|
|
|
|
if (of_property_read_u32(node,
|
|
"fsl,tclk-period", &ptp_qoriq->tclk_period) ||
|
|
of_property_read_u32(node,
|
|
"fsl,tmr-prsc", &ptp_qoriq->tmr_prsc) ||
|
|
of_property_read_u32(node,
|
|
"fsl,tmr-add", &ptp_qoriq->tmr_add) ||
|
|
of_property_read_u32(node,
|
|
"fsl,tmr-fiper1", &ptp_qoriq->tmr_fiper1) ||
|
|
of_property_read_u32(node,
|
|
"fsl,tmr-fiper2", &ptp_qoriq->tmr_fiper2) ||
|
|
of_property_read_u32(node,
|
|
"fsl,max-adj", &ptp_qoriq->caps.max_adj)) {
|
|
pr_warn("device tree node missing required elements, try automatic configuration\n");
|
|
|
|
if (ptp_qoriq_auto_config(ptp_qoriq, node))
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (of_property_read_bool(node, "little-endian")) {
|
|
ptp_qoriq->read = qoriq_read_le;
|
|
ptp_qoriq->write = qoriq_write_le;
|
|
} else {
|
|
ptp_qoriq->read = qoriq_read_be;
|
|
ptp_qoriq->write = qoriq_write_be;
|
|
}
|
|
|
|
/* The eTSEC uses differnt memory map with DPAA/ENETC */
|
|
if (of_device_is_compatible(node, "fsl,etsec-ptp")) {
|
|
ptp_qoriq->regs.ctrl_regs = base + ETSEC_CTRL_REGS_OFFSET;
|
|
ptp_qoriq->regs.alarm_regs = base + ETSEC_ALARM_REGS_OFFSET;
|
|
ptp_qoriq->regs.fiper_regs = base + ETSEC_FIPER_REGS_OFFSET;
|
|
ptp_qoriq->regs.etts_regs = base + ETSEC_ETTS_REGS_OFFSET;
|
|
} else {
|
|
ptp_qoriq->regs.ctrl_regs = base + CTRL_REGS_OFFSET;
|
|
ptp_qoriq->regs.alarm_regs = base + ALARM_REGS_OFFSET;
|
|
ptp_qoriq->regs.fiper_regs = base + FIPER_REGS_OFFSET;
|
|
ptp_qoriq->regs.etts_regs = base + ETTS_REGS_OFFSET;
|
|
}
|
|
|
|
ktime_get_real_ts64(&now);
|
|
ptp_qoriq_settime(&ptp_qoriq->caps, &now);
|
|
|
|
tmr_ctrl =
|
|
(ptp_qoriq->tclk_period & TCLK_PERIOD_MASK) << TCLK_PERIOD_SHIFT |
|
|
(ptp_qoriq->cksel & CKSEL_MASK) << CKSEL_SHIFT;
|
|
|
|
spin_lock_init(&ptp_qoriq->lock);
|
|
spin_lock_irqsave(&ptp_qoriq->lock, flags);
|
|
|
|
regs = &ptp_qoriq->regs;
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_ctrl, tmr_ctrl);
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_add, ptp_qoriq->tmr_add);
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_prsc, ptp_qoriq->tmr_prsc);
|
|
ptp_qoriq->write(®s->fiper_regs->tmr_fiper1, ptp_qoriq->tmr_fiper1);
|
|
ptp_qoriq->write(®s->fiper_regs->tmr_fiper2, ptp_qoriq->tmr_fiper2);
|
|
set_alarm(ptp_qoriq);
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_ctrl,
|
|
tmr_ctrl|FIPERST|RTPE|TE|FRD);
|
|
|
|
spin_unlock_irqrestore(&ptp_qoriq->lock, flags);
|
|
|
|
ptp_qoriq->clock = ptp_clock_register(&ptp_qoriq->caps, ptp_qoriq->dev);
|
|
if (IS_ERR(ptp_qoriq->clock))
|
|
return PTR_ERR(ptp_qoriq->clock);
|
|
|
|
ptp_qoriq->phc_index = ptp_clock_index(ptp_qoriq->clock);
|
|
ptp_qoriq_create_debugfs(ptp_qoriq);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(ptp_qoriq_init);
|
|
|
|
void ptp_qoriq_free(struct ptp_qoriq *ptp_qoriq)
|
|
{
|
|
struct ptp_qoriq_registers *regs = &ptp_qoriq->regs;
|
|
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_temask, 0);
|
|
ptp_qoriq->write(®s->ctrl_regs->tmr_ctrl, 0);
|
|
|
|
ptp_qoriq_remove_debugfs(ptp_qoriq);
|
|
ptp_clock_unregister(ptp_qoriq->clock);
|
|
iounmap(ptp_qoriq->base);
|
|
free_irq(ptp_qoriq->irq, ptp_qoriq);
|
|
}
|
|
EXPORT_SYMBOL_GPL(ptp_qoriq_free);
|
|
|
|
static int ptp_qoriq_probe(struct platform_device *dev)
|
|
{
|
|
struct ptp_qoriq *ptp_qoriq;
|
|
int err = -ENOMEM;
|
|
void __iomem *base;
|
|
|
|
ptp_qoriq = kzalloc(sizeof(*ptp_qoriq), GFP_KERNEL);
|
|
if (!ptp_qoriq)
|
|
goto no_memory;
|
|
|
|
ptp_qoriq->dev = &dev->dev;
|
|
|
|
err = -ENODEV;
|
|
|
|
ptp_qoriq->irq = platform_get_irq(dev, 0);
|
|
if (ptp_qoriq->irq < 0) {
|
|
pr_err("irq not in device tree\n");
|
|
goto no_node;
|
|
}
|
|
if (request_irq(ptp_qoriq->irq, ptp_qoriq_isr, IRQF_SHARED,
|
|
DRIVER, ptp_qoriq)) {
|
|
pr_err("request_irq failed\n");
|
|
goto no_node;
|
|
}
|
|
|
|
ptp_qoriq->rsrc = platform_get_resource(dev, IORESOURCE_MEM, 0);
|
|
if (!ptp_qoriq->rsrc) {
|
|
pr_err("no resource\n");
|
|
goto no_resource;
|
|
}
|
|
if (request_resource(&iomem_resource, ptp_qoriq->rsrc)) {
|
|
pr_err("resource busy\n");
|
|
goto no_resource;
|
|
}
|
|
|
|
base = ioremap(ptp_qoriq->rsrc->start,
|
|
resource_size(ptp_qoriq->rsrc));
|
|
if (!base) {
|
|
pr_err("ioremap ptp registers failed\n");
|
|
goto no_ioremap;
|
|
}
|
|
|
|
err = ptp_qoriq_init(ptp_qoriq, base, &ptp_qoriq_caps);
|
|
if (err)
|
|
goto no_clock;
|
|
|
|
platform_set_drvdata(dev, ptp_qoriq);
|
|
return 0;
|
|
|
|
no_clock:
|
|
iounmap(ptp_qoriq->base);
|
|
no_ioremap:
|
|
release_resource(ptp_qoriq->rsrc);
|
|
no_resource:
|
|
free_irq(ptp_qoriq->irq, ptp_qoriq);
|
|
no_node:
|
|
kfree(ptp_qoriq);
|
|
no_memory:
|
|
return err;
|
|
}
|
|
|
|
static int ptp_qoriq_remove(struct platform_device *dev)
|
|
{
|
|
struct ptp_qoriq *ptp_qoriq = platform_get_drvdata(dev);
|
|
|
|
ptp_qoriq_free(ptp_qoriq);
|
|
release_resource(ptp_qoriq->rsrc);
|
|
kfree(ptp_qoriq);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id match_table[] = {
|
|
{ .compatible = "fsl,etsec-ptp" },
|
|
{ .compatible = "fsl,fman-ptp-timer" },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, match_table);
|
|
|
|
static struct platform_driver ptp_qoriq_driver = {
|
|
.driver = {
|
|
.name = "ptp_qoriq",
|
|
.of_match_table = match_table,
|
|
},
|
|
.probe = ptp_qoriq_probe,
|
|
.remove = ptp_qoriq_remove,
|
|
};
|
|
|
|
module_platform_driver(ptp_qoriq_driver);
|
|
|
|
MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>");
|
|
MODULE_DESCRIPTION("PTP clock for Freescale QorIQ 1588 timer");
|
|
MODULE_LICENSE("GPL");
|