2022-03-04 05:01:02

by Kane Jiang

[permalink] [raw]
Subject: [PATCH V2 1/2] clocksource/drivers/timer-imx-gpt: Support i.MX GPT input capture function

From: Steve Longerbeam <[email protected]>

This patch adds support for the input capture function in the i.MX GPT.
Output compare and input capture functions are mixed in the same register
block, so we need to modify the irq ack/enable/disable primitives to not
stomp on the other function.

The input capture API is modelled after request/free irq:

typedef void (*mxc_icap_handler_t)(int, void *, ktime_t);

int mxc_request_input_capture(unsigned int chan,
mxc_icap_handler_t handler,
unsigned long capflags, void *dev_id);

- chan: the channel number being requested (0 or 1).

- handler: a callback when there is an input capture event. The
handler is given the channel number, the dev_id, and a ktime_t
marking the input capture event.

- capflags: IRQF_TRIGGER_RISING and/or IRQF_TRIGGER_FALLING. If
both are specified, events will be triggered on both rising and
falling edges of the input capture signal.

- dev_id: a context pointer given back to the handler.

void mxc_free_input_capture(unsigned int chan, void *dev_id);

This disables the given input capture channel in the GPT.

Signed-off-by: Steve Longerbeam <[email protected]>
Signed-off-by: Kane Jiang <[email protected]>
---
V2:
- add a brief commit subject
---
drivers/clocksource/timer-imx-gpt.c | 479 +++++++++++++++++++++++++---
include/linux/mxc_icap.h | 16 +
2 files changed, 445 insertions(+), 50 deletions(-)
create mode 100644 include/linux/mxc_icap.h

diff --git a/drivers/clocksource/timer-imx-gpt.c b/drivers/clocksource/timer-imx-gpt.c
index 7b2c70f2f353..c6aa1462e441 100644
--- a/drivers/clocksource/timer-imx-gpt.c
+++ b/drivers/clocksource/timer-imx-gpt.c
@@ -5,9 +5,11 @@
// Copyright (C) 2006-2007 Pavel Pisa ([email protected])
// Copyright (C) 2008 Juergen Beisert ([email protected])

+#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/clockchips.h>
+#include <linux/timecounter.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
@@ -16,6 +18,8 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/mxc_icap.h>
#include <soc/imx/timer.h>

/*
@@ -49,16 +53,52 @@
#define V2_TCTL_CLK_PER (2 << 6)
#define V2_TCTL_CLK_OSC_DIV8 (5 << 6)
#define V2_TCTL_FRR (1 << 9)
+#define V2_TCTL_IM1_BIT 16
+#define V2_TCTL_IM2_BIT 18
+#define V2_IM_DISABLE 0
+#define V2_IM_RISING 1
+#define V2_IM_FALLING 2
+#define V2_IM_BOTH 3
#define V2_TCTL_24MEN (1 << 10)
#define V2_TPRER_PRE24M 12
#define V2_IR 0x0c
+#define V2_IR_OF1 (1 << 0)
+#define V2_IR_IF1 (1 << 3)
+#define V2_IR_IF2 (1 << 4)
#define V2_TSTAT 0x08
#define V2_TSTAT_OF1 (1 << 0)
+#define V2_TSTAT_IF1 (1 << 3)
+#define V2_TSTAT_IF2 (1 << 4)
#define V2_TCN 0x24
#define V2_TCMP 0x10
+#define V2_TCAP1 0x1c
+#define V2_TCAP2 0x20

#define V2_TIMER_RATE_OSC_DIV8 3000000

+struct imx_timer;
+
+struct icap_channel {
+ struct imx_timer *imxtm;
+
+ int chan;
+
+ u32 cnt_reg;
+ u32 irqen_bit;
+ u32 status_bit;
+ u32 mode_bit;
+
+ mxc_icap_handler_t handler;
+ void *dev_id;
+
+ struct cyclecounter cc;
+ struct timecounter tc;
+};
+
+/* FIXME, for now can't find icap unless it's statically allocated */
+static struct icap_channel icap_channel[2];
+static DEFINE_SPINLOCK(icap_lock);
+
struct imx_timer {
enum imx_gpt_type type;
void __iomem *base;
@@ -73,12 +113,20 @@ struct imx_gpt_data {
int reg_tstat;
int reg_tcn;
int reg_tcmp;
- void (*gpt_setup_tctl)(struct imx_timer *imxtm);
- void (*gpt_irq_enable)(struct imx_timer *imxtm);
- void (*gpt_irq_disable)(struct imx_timer *imxtm);
- void (*gpt_irq_acknowledge)(struct imx_timer *imxtm);
+ void (*gpt_oc_setup_tctl)(struct imx_timer *imxtm);
+ void (*gpt_oc_irq_enable)(struct imx_timer *imxtm);
+ void (*gpt_oc_irq_disable)(struct imx_timer *imxtm);
+ void (*gpt_oc_irq_acknowledge)(struct imx_timer *imxtm);
+ bool (*gpt_is_oc_irq)(struct imx_timer *imxtm, unsigned int tstat);
int (*set_next_event)(unsigned long evt,
struct clock_event_device *ced);
+
+ void (*gpt_ic_irq_enable)(struct icap_channel *ic);
+ void (*gpt_ic_irq_disable)(struct icap_channel *ic);
+ void (*gpt_ic_irq_acknowledge)(struct icap_channel *ic);
+ bool (*gpt_is_ic_irq)(struct icap_channel *ic, unsigned int tstat);
+ void (*gpt_ic_enable)(struct icap_channel *ic, unsigned int mode);
+ void (*gpt_ic_disable)(struct icap_channel *ic);
};

static inline struct imx_timer *to_imx_timer(struct clock_event_device *ced)
@@ -86,52 +134,144 @@ static inline struct imx_timer *to_imx_timer(struct clock_event_device *ced)
return container_of(ced, struct imx_timer, ced);
}

-static void imx1_gpt_irq_disable(struct imx_timer *imxtm)
+static void imx1_gpt_oc_irq_disable(struct imx_timer *imxtm)
{
unsigned int tmp;

tmp = readl_relaxed(imxtm->base + MXC_TCTL);
writel_relaxed(tmp & ~MX1_2_TCTL_IRQEN, imxtm->base + MXC_TCTL);
}
-#define imx21_gpt_irq_disable imx1_gpt_irq_disable
+#define imx21_gpt_oc_irq_disable imx1_gpt_oc_irq_disable

-static void imx31_gpt_irq_disable(struct imx_timer *imxtm)
+static void imx31_gpt_oc_irq_disable(struct imx_timer *imxtm)
{
- writel_relaxed(0, imxtm->base + V2_IR);
+ unsigned int tmp;
+
+ tmp = readl_relaxed(imxtm->base + V2_IR);
+ writel_relaxed(tmp & ~V2_IR_OF1, imxtm->base + V2_IR);
}
-#define imx6dl_gpt_irq_disable imx31_gpt_irq_disable
+#define imx6dl_gpt_oc_irq_disable imx31_gpt_oc_irq_disable

-static void imx1_gpt_irq_enable(struct imx_timer *imxtm)
+static void imx1_gpt_oc_irq_enable(struct imx_timer *imxtm)
{
unsigned int tmp;

tmp = readl_relaxed(imxtm->base + MXC_TCTL);
writel_relaxed(tmp | MX1_2_TCTL_IRQEN, imxtm->base + MXC_TCTL);
}
-#define imx21_gpt_irq_enable imx1_gpt_irq_enable
+#define imx21_gpt_oc_irq_enable imx1_gpt_oc_irq_enable

-static void imx31_gpt_irq_enable(struct imx_timer *imxtm)
+static void imx31_gpt_oc_irq_enable(struct imx_timer *imxtm)
{
- writel_relaxed(1<<0, imxtm->base + V2_IR);
+ unsigned int tmp;
+
+ tmp = readl_relaxed(imxtm->base + V2_IR);
+ writel_relaxed(tmp | V2_IR_OF1, imxtm->base + V2_IR);
}
-#define imx6dl_gpt_irq_enable imx31_gpt_irq_enable
+#define imx6dl_gpt_oc_irq_enable imx31_gpt_oc_irq_enable

-static void imx1_gpt_irq_acknowledge(struct imx_timer *imxtm)
+static void imx1_gpt_oc_irq_acknowledge(struct imx_timer *imxtm)
{
writel_relaxed(0, imxtm->base + MX1_2_TSTAT);
}

-static void imx21_gpt_irq_acknowledge(struct imx_timer *imxtm)
+static void imx21_gpt_oc_irq_acknowledge(struct imx_timer *imxtm)
{
writel_relaxed(MX2_TSTAT_CAPT | MX2_TSTAT_COMP,
imxtm->base + MX1_2_TSTAT);
}

-static void imx31_gpt_irq_acknowledge(struct imx_timer *imxtm)
+static bool imx1_gpt_is_oc_irq(struct imx_timer *imxtm, unsigned int tstat)
+{
+ return true;
+}
+
+static bool imx21_gpt_is_oc_irq(struct imx_timer *imxtm, unsigned int tstat)
+{
+ return (tstat & MX2_TSTAT_COMP) != 0;
+}
+
+static bool imx31_gpt_is_oc_irq(struct imx_timer *imxtm, unsigned int tstat)
+{
+ return (tstat & V2_TSTAT_OF1) != 0;
+}
+#define imx6dl_gpt_is_oc_irq imx31_gpt_is_oc_irq
+
+static void imx31_gpt_oc_irq_acknowledge(struct imx_timer *imxtm)
{
writel_relaxed(V2_TSTAT_OF1, imxtm->base + V2_TSTAT);
}
-#define imx6dl_gpt_irq_acknowledge imx31_gpt_irq_acknowledge
+#define imx6dl_gpt_oc_irq_acknowledge imx31_gpt_oc_irq_acknowledge
+
+static void imx31_gpt_ic_irq_disable(struct icap_channel *ic)
+{
+ struct imx_timer *imxtm = ic->imxtm;
+ unsigned int tmp;
+
+ tmp = readl_relaxed(imxtm->base + V2_IR);
+ tmp &= ~ic->irqen_bit;
+ writel_relaxed(tmp, imxtm->base + V2_IR);
+}
+#define imx6dl_gpt_ic_irq_disable imx31_gpt_ic_irq_disable
+
+static void imx31_gpt_ic_irq_enable(struct icap_channel *ic)
+{
+ struct imx_timer *imxtm = ic->imxtm;
+ unsigned int tmp;
+
+ tmp = readl_relaxed(imxtm->base + V2_IR);
+ tmp |= ic->irqen_bit;
+ writel_relaxed(tmp, imxtm->base + V2_IR);
+}
+#define imx6dl_gpt_ic_irq_enable imx31_gpt_ic_irq_enable
+
+static void imx31_gpt_ic_irq_acknowledge(struct icap_channel *ic)
+{
+ struct imx_timer *imxtm = ic->imxtm;
+
+ writel_relaxed(ic->status_bit, imxtm->base + V2_TSTAT);
+}
+#define imx6dl_gpt_ic_irq_acknowledge imx31_gpt_ic_irq_acknowledge
+
+static bool imx1_gpt_is_ic_irq(struct icap_channel *ic, unsigned int tstat)
+{
+ return false;
+}
+#define imx21_gpt_is_ic_irq imx1_gpt_is_ic_irq
+
+static bool imx31_gpt_is_ic_irq(struct icap_channel *ic, unsigned int tstat)
+{
+ return (tstat & ic->status_bit) != 0;
+}
+#define imx6dl_gpt_is_ic_irq imx31_gpt_is_ic_irq
+
+static void imx31_gpt_ic_enable(struct icap_channel *ic, unsigned int mode)
+{
+ struct imx_timer *imxtm = ic->imxtm;
+ unsigned int tctl, mask;
+
+ mask = 0x3 << ic->mode_bit;
+ mode <<= ic->mode_bit;
+
+ tctl = readl_relaxed(imxtm->base + MXC_TCTL);
+ tctl &= ~mask;
+ tctl |= mode;
+ writel_relaxed(tctl, imxtm->base + MXC_TCTL);
+}
+#define imx6dl_gpt_ic_enable imx31_gpt_ic_enable
+
+static void imx31_gpt_ic_disable(struct icap_channel *ic)
+{
+ struct imx_timer *imxtm = ic->imxtm;
+ unsigned int tctl, mask;
+
+ mask = 0x3 << ic->mode_bit;
+
+ tctl = readl_relaxed(imxtm->base + MXC_TCTL);
+ tctl &= ~mask;
+ writel_relaxed(tctl, imxtm->base + MXC_TCTL);
+}
+#define imx6dl_gpt_ic_disable imx31_gpt_ic_disable

static void __iomem *sched_clock_reg;

@@ -149,6 +289,19 @@ static unsigned long imx_read_current_timer(void)
}
#endif

+static u64 mxc_clocksource_read(struct clocksource *cs)
+{
+ return mxc_read_sched_clock();
+}
+
+static struct clocksource clocksource_mxc = {
+ .name = "mxc_timer1",
+ .rating = 200,
+ .mask = CLOCKSOURCE_MASK(32),
+ .read = mxc_clocksource_read,
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
static int __init mxc_clocksource_init(struct imx_timer *imxtm)
{
unsigned int c = clk_get_rate(imxtm->clk_per);
@@ -163,8 +316,7 @@ static int __init mxc_clocksource_init(struct imx_timer *imxtm)
sched_clock_reg = reg;

sched_clock_register(mxc_read_sched_clock, 32, c);
- return clocksource_mmio_init(reg, "mxc_timer1", c, 200, 32,
- clocksource_mmio_readl_up);
+ return clocksource_register_hz(&clocksource_mxc, c);
}

/* clock event */
@@ -204,14 +356,14 @@ static int mxc_shutdown(struct clock_event_device *ced)
u32 tcn;

/* Disable interrupt in GPT module */
- imxtm->gpt->gpt_irq_disable(imxtm);
+ imxtm->gpt->gpt_oc_irq_disable(imxtm);

tcn = readl_relaxed(imxtm->base + imxtm->gpt->reg_tcn);
/* Set event time into far-far future */
writel_relaxed(tcn - 3, imxtm->base + imxtm->gpt->reg_tcmp);

/* Clear pending interrupt */
- imxtm->gpt->gpt_irq_acknowledge(imxtm);
+ imxtm->gpt->gpt_oc_irq_acknowledge(imxtm);

#ifdef DEBUG
printk(KERN_INFO "%s: changing mode\n", __func__);
@@ -225,7 +377,7 @@ static int mxc_set_oneshot(struct clock_event_device *ced)
struct imx_timer *imxtm = to_imx_timer(ced);

/* Disable interrupt in GPT module */
- imxtm->gpt->gpt_irq_disable(imxtm);
+ imxtm->gpt->gpt_oc_irq_disable(imxtm);

if (!clockevent_state_oneshot(ced)) {
u32 tcn = readl_relaxed(imxtm->base + imxtm->gpt->reg_tcn);
@@ -233,7 +385,7 @@ static int mxc_set_oneshot(struct clock_event_device *ced)
writel_relaxed(tcn - 3, imxtm->base + imxtm->gpt->reg_tcmp);

/* Clear pending interrupt */
- imxtm->gpt->gpt_irq_acknowledge(imxtm);
+ imxtm->gpt->gpt_oc_irq_acknowledge(imxtm);
}

#ifdef DEBUG
@@ -246,7 +398,7 @@ static int mxc_set_oneshot(struct clock_event_device *ced)
* to call mxc_set_next_event() or shutdown clock after
* mode switching
*/
- imxtm->gpt->gpt_irq_enable(imxtm);
+ imxtm->gpt->gpt_oc_irq_enable(imxtm);

return 0;
}
@@ -259,12 +411,29 @@ static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id)
struct clock_event_device *ced = dev_id;
struct imx_timer *imxtm = to_imx_timer(ced);
uint32_t tstat;
+ int i;

tstat = readl_relaxed(imxtm->base + imxtm->gpt->reg_tstat);

- imxtm->gpt->gpt_irq_acknowledge(imxtm);
+ for (i = 0; i < 2; i++) {
+ struct icap_channel *ic = &icap_channel[i];
+ ktime_t timestamp;

- ced->event_handler(ced);
+ if (!imxtm->gpt->gpt_is_ic_irq(ic, tstat))
+ continue;
+
+ imxtm->gpt->gpt_ic_irq_acknowledge(ic);
+
+ timestamp = ns_to_ktime(timecounter_read(&ic->tc));
+
+ if (ic->handler)
+ ic->handler(ic->chan, ic->dev_id, timestamp);
+ }
+
+ if (imxtm->gpt->gpt_is_oc_irq(imxtm, tstat)) {
+ imxtm->gpt->gpt_oc_irq_acknowledge(imxtm);
+ ced->event_handler(ced);
+ }

return IRQ_HANDLED;
}
@@ -289,16 +458,16 @@ static int __init mxc_clockevent_init(struct imx_timer *imxtm)
IRQF_TIMER | IRQF_IRQPOLL, "i.MX Timer Tick", ced);
}

-static void imx1_gpt_setup_tctl(struct imx_timer *imxtm)
+static void imx1_gpt_oc_setup_tctl(struct imx_timer *imxtm)
{
u32 tctl_val;

tctl_val = MX1_2_TCTL_FRR | MX1_2_TCTL_CLK_PCLK1 | MXC_TCTL_TEN;
writel_relaxed(tctl_val, imxtm->base + MXC_TCTL);
}
-#define imx21_gpt_setup_tctl imx1_gpt_setup_tctl
+#define imx21_gpt_oc_setup_tctl imx1_gpt_oc_setup_tctl

-static void imx31_gpt_setup_tctl(struct imx_timer *imxtm)
+static void imx31_gpt_oc_setup_tctl(struct imx_timer *imxtm)
{
u32 tctl_val;

@@ -311,7 +480,7 @@ static void imx31_gpt_setup_tctl(struct imx_timer *imxtm)
writel_relaxed(tctl_val, imxtm->base + MXC_TCTL);
}

-static void imx6dl_gpt_setup_tctl(struct imx_timer *imxtm)
+static void imx6dl_gpt_oc_setup_tctl(struct imx_timer *imxtm)
{
u32 tctl_val;

@@ -332,10 +501,12 @@ static const struct imx_gpt_data imx1_gpt_data = {
.reg_tstat = MX1_2_TSTAT,
.reg_tcn = MX1_2_TCN,
.reg_tcmp = MX1_2_TCMP,
- .gpt_irq_enable = imx1_gpt_irq_enable,
- .gpt_irq_disable = imx1_gpt_irq_disable,
- .gpt_irq_acknowledge = imx1_gpt_irq_acknowledge,
- .gpt_setup_tctl = imx1_gpt_setup_tctl,
+ .gpt_oc_irq_enable = imx1_gpt_oc_irq_enable,
+ .gpt_oc_irq_disable = imx1_gpt_oc_irq_disable,
+ .gpt_oc_irq_acknowledge = imx1_gpt_oc_irq_acknowledge,
+ .gpt_is_oc_irq = imx1_gpt_is_oc_irq,
+ .gpt_is_ic_irq = imx1_gpt_is_ic_irq,
+ .gpt_oc_setup_tctl = imx1_gpt_oc_setup_tctl,
.set_next_event = mx1_2_set_next_event,
};

@@ -343,10 +514,12 @@ static const struct imx_gpt_data imx21_gpt_data = {
.reg_tstat = MX1_2_TSTAT,
.reg_tcn = MX1_2_TCN,
.reg_tcmp = MX1_2_TCMP,
- .gpt_irq_enable = imx21_gpt_irq_enable,
- .gpt_irq_disable = imx21_gpt_irq_disable,
- .gpt_irq_acknowledge = imx21_gpt_irq_acknowledge,
- .gpt_setup_tctl = imx21_gpt_setup_tctl,
+ .gpt_oc_irq_enable = imx21_gpt_oc_irq_enable,
+ .gpt_oc_irq_disable = imx21_gpt_oc_irq_disable,
+ .gpt_oc_irq_acknowledge = imx21_gpt_oc_irq_acknowledge,
+ .gpt_is_oc_irq = imx21_gpt_is_oc_irq,
+ .gpt_is_ic_irq = imx21_gpt_is_ic_irq,
+ .gpt_oc_setup_tctl = imx21_gpt_oc_setup_tctl,
.set_next_event = mx1_2_set_next_event,
};

@@ -354,27 +527,160 @@ static const struct imx_gpt_data imx31_gpt_data = {
.reg_tstat = V2_TSTAT,
.reg_tcn = V2_TCN,
.reg_tcmp = V2_TCMP,
- .gpt_irq_enable = imx31_gpt_irq_enable,
- .gpt_irq_disable = imx31_gpt_irq_disable,
- .gpt_irq_acknowledge = imx31_gpt_irq_acknowledge,
- .gpt_setup_tctl = imx31_gpt_setup_tctl,
+ .gpt_oc_irq_enable = imx31_gpt_oc_irq_enable,
+ .gpt_oc_irq_disable = imx31_gpt_oc_irq_disable,
+ .gpt_oc_irq_acknowledge = imx31_gpt_oc_irq_acknowledge,
+ .gpt_is_oc_irq = imx31_gpt_is_oc_irq,
+ .gpt_oc_setup_tctl = imx31_gpt_oc_setup_tctl,
.set_next_event = v2_set_next_event,
+
+ /* input capture methods */
+ .gpt_ic_irq_enable = imx31_gpt_ic_irq_enable,
+ .gpt_ic_irq_disable = imx31_gpt_ic_irq_disable,
+ .gpt_ic_irq_acknowledge = imx31_gpt_ic_irq_acknowledge,
+ .gpt_is_ic_irq = imx31_gpt_is_ic_irq,
+ .gpt_ic_enable = imx31_gpt_ic_enable,
+ .gpt_ic_disable = imx31_gpt_ic_disable,
};

static const struct imx_gpt_data imx6dl_gpt_data = {
.reg_tstat = V2_TSTAT,
.reg_tcn = V2_TCN,
.reg_tcmp = V2_TCMP,
- .gpt_irq_enable = imx6dl_gpt_irq_enable,
- .gpt_irq_disable = imx6dl_gpt_irq_disable,
- .gpt_irq_acknowledge = imx6dl_gpt_irq_acknowledge,
- .gpt_setup_tctl = imx6dl_gpt_setup_tctl,
+ .gpt_oc_irq_enable = imx6dl_gpt_oc_irq_enable,
+ .gpt_oc_irq_disable = imx6dl_gpt_oc_irq_disable,
+ .gpt_oc_irq_acknowledge = imx6dl_gpt_oc_irq_acknowledge,
+ .gpt_is_oc_irq = imx6dl_gpt_is_oc_irq,
+ .gpt_oc_setup_tctl = imx6dl_gpt_oc_setup_tctl,
.set_next_event = v2_set_next_event,
+
+ /* input capture methods */
+ .gpt_ic_irq_enable = imx6dl_gpt_ic_irq_enable,
+ .gpt_ic_irq_disable = imx6dl_gpt_ic_irq_disable,
+ .gpt_ic_irq_acknowledge = imx6dl_gpt_ic_irq_acknowledge,
+ .gpt_is_ic_irq = imx6dl_gpt_is_ic_irq,
+ .gpt_ic_enable = imx6dl_gpt_ic_enable,
+ .gpt_ic_disable = imx6dl_gpt_ic_disable,
};

+static u64 gpt_ic_read(const struct cyclecounter *cc)
+{
+ struct icap_channel *ic = container_of(cc, struct icap_channel, cc);
+ struct imx_timer *imxtm = ic->imxtm;
+
+ return readl_relaxed(imxtm->base + ic->cnt_reg);
+}
+
+int mxc_request_input_capture(unsigned int chan, mxc_icap_handler_t handler,
+ unsigned long capflags, void *dev_id)
+{
+ struct imx_timer *imxtm;
+ struct icap_channel *ic;
+ unsigned long flags;
+ u64 start_cycles;
+ int ret = 0;
+ u32 mode;
+
+ /* we only care about rising and falling flags */
+ capflags &= (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING);
+
+ if (chan > 1 || !handler || !capflags)
+ return -EINVAL;
+
+ ic = &icap_channel[chan];
+ imxtm = ic->imxtm;
+
+ if (!imxtm->gpt->gpt_ic_enable)
+ return -ENODEV;
+
+ spin_lock_irqsave(&icap_lock, flags);
+
+ if (ic->handler) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ic->handler = handler;
+ ic->dev_id = dev_id;
+
+ switch (capflags) {
+ case IRQF_TRIGGER_RISING:
+ mode = V2_IM_RISING;
+ break;
+ case IRQF_TRIGGER_FALLING:
+ mode = V2_IM_FALLING;
+ break;
+ default:
+ mode = V2_IM_BOTH;
+ break;
+ }
+
+ /* ack any pending input capture interrupt before enabling */
+ imxtm->gpt->gpt_ic_irq_acknowledge(ic);
+
+ /*
+ * initialize the cyclecounter. The input capture is capturing
+ * from the mxc clocksource, so it has the same mask/shift/mult.
+ */
+ memset(&ic->cc, 0, sizeof(ic->cc));
+ ic->cc.read = gpt_ic_read;
+ ic->cc.mask = clocksource_mxc.mask;
+ ic->cc.shift = clocksource_mxc.shift;
+ ic->cc.mult = clocksource_mxc.mult;
+
+ /* initialize a timecounter for the input capture */
+ start_cycles = mxc_read_sched_clock();
+ timecounter_init(&ic->tc, &ic->cc, ktime_get_ns());
+ /*
+ * timecounter_init() read the last captured timer count, but
+ * that's not the start cycle counter, so update it with the
+ * real start cycles.
+ */
+ ic->tc.cycle_last = start_cycles;
+
+ imxtm->gpt->gpt_ic_enable(ic, mode);
+ imxtm->gpt->gpt_ic_irq_enable(ic);
+
+out:
+ spin_unlock_irqrestore(&icap_lock, flags);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mxc_request_input_capture);
+
+void mxc_free_input_capture(unsigned int chan, void *dev_id)
+{
+ struct imx_timer *imxtm;
+ struct icap_channel *ic;
+ unsigned long flags;
+
+ if (chan > 1)
+ return;
+
+ ic = &icap_channel[chan];
+ imxtm = ic->imxtm;
+
+ if (!imxtm->gpt->gpt_ic_disable)
+ return;
+
+ spin_lock_irqsave(&icap_lock, flags);
+
+ if (!ic->handler || dev_id != ic->dev_id)
+ goto out;
+
+ imxtm->gpt->gpt_ic_irq_disable(ic);
+ imxtm->gpt->gpt_ic_disable(ic);
+
+ ic->handler = NULL;
+ ic->dev_id = NULL;
+out:
+ spin_unlock_irqrestore(&icap_lock, flags);
+}
+EXPORT_SYMBOL_GPL(mxc_free_input_capture);
+
static int __init _mxc_timer_init(struct imx_timer *imxtm)
{
- int ret;
+ struct icap_channel *ic;
+ int i, ret;

switch (imxtm->type) {
case GPT_TYPE_IMX1:
@@ -410,14 +716,23 @@ static int __init _mxc_timer_init(struct imx_timer *imxtm)
writel_relaxed(0, imxtm->base + MXC_TCTL);
writel_relaxed(0, imxtm->base + MXC_TPRER); /* see datasheet note */

- imxtm->gpt->gpt_setup_tctl(imxtm);
+ imxtm->gpt->gpt_oc_setup_tctl(imxtm);

/* init and register the timer to the framework */
ret = mxc_clocksource_init(imxtm);
if (ret)
return ret;

- return mxc_clockevent_init(imxtm);
+ ret = mxc_clockevent_init(imxtm);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < 2; i++) {
+ ic = &icap_channel[i];
+ ic->imxtm = imxtm;
+ }
+
+ return 0;
}

void __init mxc_timer_init(unsigned long pbase, int irq, enum imx_gpt_type type)
@@ -439,6 +754,70 @@ void __init mxc_timer_init(unsigned long pbase, int irq, enum imx_gpt_type type)
_mxc_timer_init(imxtm);
}

+/*
+ * a platform driver is needed in order to acquire pinmux
+ * for input capture pins. The probe call is also useful
+ * for setting up the input capture channel structures.
+ */
+static int mxc_timer_probe(struct platform_device *pdev)
+{
+ struct icap_channel *ic;
+ int i;
+
+ /* setup the input capture channels */
+ for (i = 0; i < 2; i++) {
+ ic = &icap_channel[i];
+ ic->chan = i;
+ if (i == 0) {
+ ic->cnt_reg = V2_TCAP1;
+ ic->irqen_bit = V2_IR_IF1;
+ ic->status_bit = V2_TSTAT_IF1;
+ ic->mode_bit = V2_TCTL_IM1_BIT;
+ } else {
+ ic->cnt_reg = V2_TCAP2;
+ ic->irqen_bit = V2_IR_IF2;
+ ic->status_bit = V2_TSTAT_IF2;
+ ic->mode_bit = V2_TCTL_IM2_BIT;
+ }
+ }
+
+ return 0;
+}
+
+static int mxc_timer_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static const struct of_device_id timer_of_match[] = {
+ { .compatible = "fsl,imx1-gpt" },
+ { .compatible = "fsl,imx21-gpt" },
+ { .compatible = "fsl,imx27-gpt" },
+ { .compatible = "fsl,imx31-gpt" },
+ { .compatible = "fsl,imx25-gpt" },
+ { .compatible = "fsl,imx50-gpt" },
+ { .compatible = "fsl,imx51-gpt" },
+ { .compatible = "fsl,imx53-gpt" },
+ { .compatible = "fsl,imx6q-gpt" },
+ { .compatible = "fsl,imx6dl-gpt" },
+ { .compatible = "fsl,imx6sl-gpt" },
+ { .compatible = "fsl,imx6sx-gpt" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, timer_of_match);
+
+static struct platform_driver mxc_timer_pdrv = {
+ .probe = mxc_timer_probe,
+ .remove = mxc_timer_remove,
+ .driver = {
+ .name = "mxc-timer",
+ .owner = THIS_MODULE,
+ .of_match_table = timer_of_match,
+ },
+};
+
+module_platform_driver(mxc_timer_pdrv);
+
static int __init mxc_timer_init_dt(struct device_node *np, enum imx_gpt_type type)
{
struct imx_timer *imxtm;
diff --git a/include/linux/mxc_icap.h b/include/linux/mxc_icap.h
new file mode 100644
index 000000000000..fa5ffdf3b589
--- /dev/null
+++ b/include/linux/mxc_icap.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * i.MX GPT Input Capture support.
+ *
+ * Copyright (C) 2015 Mentor Graphics, Inc. All Rights Reserved.
+ */
+#ifndef __MXC_ICAP_H__
+#define __MXC_ICAP_H__
+
+typedef void (*mxc_icap_handler_t)(int, void *, ktime_t);
+
+int mxc_request_input_capture(unsigned int chan, mxc_icap_handler_t handler,
+ unsigned long capflags, void *dev_id);
+void mxc_free_input_capture(unsigned int chan, void *dev_id);
+
+#endif
--
2.25.1


2022-03-17 20:41:56

by Ahmad Fatoum

[permalink] [raw]
Subject: Re: [PATCH V2 1/2] clocksource/drivers/timer-imx-gpt: Support i.MX GPT input capture function

Hello Kane,

On 04.03.22 02:56, Kane Jiang wrote:
> From: Steve Longerbeam <[email protected]>
>
> This patch adds support for the input capture function in the i.MX GPT.
> Output compare and input capture functions are mixed in the same register
> block, so we need to modify the irq ack/enable/disable primitives to not
> stomp on the other function.
>
> The input capture API is modelled after request/free irq:
>
> typedef void (*mxc_icap_handler_t)(int, void *, ktime_t);
>
> int mxc_request_input_capture(unsigned int chan,
> mxc_icap_handler_t handler,
> unsigned long capflags, void *dev_id);
>
> - chan: the channel number being requested (0 or 1).
>
> - handler: a callback when there is an input capture event. The
> handler is given the channel number, the dev_id, and a ktime_t
> marking the input capture event.
>
> - capflags: IRQF_TRIGGER_RISING and/or IRQF_TRIGGER_FALLING. If
> both are specified, events will be triggered on both rising and
> falling edges of the input capture signal.
>
> - dev_id: a context pointer given back to the handler.
>
> void mxc_free_input_capture(unsigned int chan, void *dev_id);
>
> This disables the given input capture channel in the GPT.
>
> Signed-off-by: Steve Longerbeam <[email protected]>
> Signed-off-by: Kane Jiang <[email protected]>

Judging from the Copyright year, you've been carrying this patch for a while.

In the meantime, a counter subsystem has been merged into the kernel, which
is likely what you should interface with here, instead of bolting a new in-kernel
API onto a clocksource driver without any upstream users.

Cheers,
Ahmad

> ---
> V2:
> - add a brief commit subject
> ---
> drivers/clocksource/timer-imx-gpt.c | 479 +++++++++++++++++++++++++---
> include/linux/mxc_icap.h | 16 +
> 2 files changed, 445 insertions(+), 50 deletions(-)
> create mode 100644 include/linux/mxc_icap.h
>
> diff --git a/drivers/clocksource/timer-imx-gpt.c b/drivers/clocksource/timer-imx-gpt.c
> index 7b2c70f2f353..c6aa1462e441 100644
> --- a/drivers/clocksource/timer-imx-gpt.c
> +++ b/drivers/clocksource/timer-imx-gpt.c
> @@ -5,9 +5,11 @@
> // Copyright (C) 2006-2007 Pavel Pisa ([email protected])
> // Copyright (C) 2008 Juergen Beisert ([email protected])
>
> +#include <linux/module.h>
> #include <linux/interrupt.h>
> #include <linux/irq.h>
> #include <linux/clockchips.h>
> +#include <linux/timecounter.h>
> #include <linux/clk.h>
> #include <linux/delay.h>
> #include <linux/err.h>
> @@ -16,6 +18,8 @@
> #include <linux/of.h>
> #include <linux/of_address.h>
> #include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/mxc_icap.h>
> #include <soc/imx/timer.h>
>
> /*
> @@ -49,16 +53,52 @@
> #define V2_TCTL_CLK_PER (2 << 6)
> #define V2_TCTL_CLK_OSC_DIV8 (5 << 6)
> #define V2_TCTL_FRR (1 << 9)
> +#define V2_TCTL_IM1_BIT 16
> +#define V2_TCTL_IM2_BIT 18
> +#define V2_IM_DISABLE 0
> +#define V2_IM_RISING 1
> +#define V2_IM_FALLING 2
> +#define V2_IM_BOTH 3
> #define V2_TCTL_24MEN (1 << 10)
> #define V2_TPRER_PRE24M 12
> #define V2_IR 0x0c
> +#define V2_IR_OF1 (1 << 0)
> +#define V2_IR_IF1 (1 << 3)
> +#define V2_IR_IF2 (1 << 4)
> #define V2_TSTAT 0x08
> #define V2_TSTAT_OF1 (1 << 0)
> +#define V2_TSTAT_IF1 (1 << 3)
> +#define V2_TSTAT_IF2 (1 << 4)
> #define V2_TCN 0x24
> #define V2_TCMP 0x10
> +#define V2_TCAP1 0x1c
> +#define V2_TCAP2 0x20
>
> #define V2_TIMER_RATE_OSC_DIV8 3000000
>
> +struct imx_timer;
> +
> +struct icap_channel {
> + struct imx_timer *imxtm;
> +
> + int chan;
> +
> + u32 cnt_reg;
> + u32 irqen_bit;
> + u32 status_bit;
> + u32 mode_bit;
> +
> + mxc_icap_handler_t handler;
> + void *dev_id;
> +
> + struct cyclecounter cc;
> + struct timecounter tc;
> +};
> +
> +/* FIXME, for now can't find icap unless it's statically allocated */
> +static struct icap_channel icap_channel[2];
> +static DEFINE_SPINLOCK(icap_lock);
> +
> struct imx_timer {
> enum imx_gpt_type type;
> void __iomem *base;
> @@ -73,12 +113,20 @@ struct imx_gpt_data {
> int reg_tstat;
> int reg_tcn;
> int reg_tcmp;
> - void (*gpt_setup_tctl)(struct imx_timer *imxtm);
> - void (*gpt_irq_enable)(struct imx_timer *imxtm);
> - void (*gpt_irq_disable)(struct imx_timer *imxtm);
> - void (*gpt_irq_acknowledge)(struct imx_timer *imxtm);
> + void (*gpt_oc_setup_tctl)(struct imx_timer *imxtm);
> + void (*gpt_oc_irq_enable)(struct imx_timer *imxtm);
> + void (*gpt_oc_irq_disable)(struct imx_timer *imxtm);
> + void (*gpt_oc_irq_acknowledge)(struct imx_timer *imxtm);
> + bool (*gpt_is_oc_irq)(struct imx_timer *imxtm, unsigned int tstat);
> int (*set_next_event)(unsigned long evt,
> struct clock_event_device *ced);
> +
> + void (*gpt_ic_irq_enable)(struct icap_channel *ic);
> + void (*gpt_ic_irq_disable)(struct icap_channel *ic);
> + void (*gpt_ic_irq_acknowledge)(struct icap_channel *ic);
> + bool (*gpt_is_ic_irq)(struct icap_channel *ic, unsigned int tstat);
> + void (*gpt_ic_enable)(struct icap_channel *ic, unsigned int mode);
> + void (*gpt_ic_disable)(struct icap_channel *ic);
> };
>
> static inline struct imx_timer *to_imx_timer(struct clock_event_device *ced)
> @@ -86,52 +134,144 @@ static inline struct imx_timer *to_imx_timer(struct clock_event_device *ced)
> return container_of(ced, struct imx_timer, ced);
> }
>
> -static void imx1_gpt_irq_disable(struct imx_timer *imxtm)
> +static void imx1_gpt_oc_irq_disable(struct imx_timer *imxtm)
> {
> unsigned int tmp;
>
> tmp = readl_relaxed(imxtm->base + MXC_TCTL);
> writel_relaxed(tmp & ~MX1_2_TCTL_IRQEN, imxtm->base + MXC_TCTL);
> }
> -#define imx21_gpt_irq_disable imx1_gpt_irq_disable
> +#define imx21_gpt_oc_irq_disable imx1_gpt_oc_irq_disable
>
> -static void imx31_gpt_irq_disable(struct imx_timer *imxtm)
> +static void imx31_gpt_oc_irq_disable(struct imx_timer *imxtm)
> {
> - writel_relaxed(0, imxtm->base + V2_IR);
> + unsigned int tmp;
> +
> + tmp = readl_relaxed(imxtm->base + V2_IR);
> + writel_relaxed(tmp & ~V2_IR_OF1, imxtm->base + V2_IR);
> }
> -#define imx6dl_gpt_irq_disable imx31_gpt_irq_disable
> +#define imx6dl_gpt_oc_irq_disable imx31_gpt_oc_irq_disable
>
> -static void imx1_gpt_irq_enable(struct imx_timer *imxtm)
> +static void imx1_gpt_oc_irq_enable(struct imx_timer *imxtm)
> {
> unsigned int tmp;
>
> tmp = readl_relaxed(imxtm->base + MXC_TCTL);
> writel_relaxed(tmp | MX1_2_TCTL_IRQEN, imxtm->base + MXC_TCTL);
> }
> -#define imx21_gpt_irq_enable imx1_gpt_irq_enable
> +#define imx21_gpt_oc_irq_enable imx1_gpt_oc_irq_enable
>
> -static void imx31_gpt_irq_enable(struct imx_timer *imxtm)
> +static void imx31_gpt_oc_irq_enable(struct imx_timer *imxtm)
> {
> - writel_relaxed(1<<0, imxtm->base + V2_IR);
> + unsigned int tmp;
> +
> + tmp = readl_relaxed(imxtm->base + V2_IR);
> + writel_relaxed(tmp | V2_IR_OF1, imxtm->base + V2_IR);
> }
> -#define imx6dl_gpt_irq_enable imx31_gpt_irq_enable
> +#define imx6dl_gpt_oc_irq_enable imx31_gpt_oc_irq_enable
>
> -static void imx1_gpt_irq_acknowledge(struct imx_timer *imxtm)
> +static void imx1_gpt_oc_irq_acknowledge(struct imx_timer *imxtm)
> {
> writel_relaxed(0, imxtm->base + MX1_2_TSTAT);
> }
>
> -static void imx21_gpt_irq_acknowledge(struct imx_timer *imxtm)
> +static void imx21_gpt_oc_irq_acknowledge(struct imx_timer *imxtm)
> {
> writel_relaxed(MX2_TSTAT_CAPT | MX2_TSTAT_COMP,
> imxtm->base + MX1_2_TSTAT);
> }
>
> -static void imx31_gpt_irq_acknowledge(struct imx_timer *imxtm)
> +static bool imx1_gpt_is_oc_irq(struct imx_timer *imxtm, unsigned int tstat)
> +{
> + return true;
> +}
> +
> +static bool imx21_gpt_is_oc_irq(struct imx_timer *imxtm, unsigned int tstat)
> +{
> + return (tstat & MX2_TSTAT_COMP) != 0;
> +}
> +
> +static bool imx31_gpt_is_oc_irq(struct imx_timer *imxtm, unsigned int tstat)
> +{
> + return (tstat & V2_TSTAT_OF1) != 0;
> +}
> +#define imx6dl_gpt_is_oc_irq imx31_gpt_is_oc_irq
> +
> +static void imx31_gpt_oc_irq_acknowledge(struct imx_timer *imxtm)
> {
> writel_relaxed(V2_TSTAT_OF1, imxtm->base + V2_TSTAT);
> }
> -#define imx6dl_gpt_irq_acknowledge imx31_gpt_irq_acknowledge
> +#define imx6dl_gpt_oc_irq_acknowledge imx31_gpt_oc_irq_acknowledge
> +
> +static void imx31_gpt_ic_irq_disable(struct icap_channel *ic)
> +{
> + struct imx_timer *imxtm = ic->imxtm;
> + unsigned int tmp;
> +
> + tmp = readl_relaxed(imxtm->base + V2_IR);
> + tmp &= ~ic->irqen_bit;
> + writel_relaxed(tmp, imxtm->base + V2_IR);
> +}
> +#define imx6dl_gpt_ic_irq_disable imx31_gpt_ic_irq_disable
> +
> +static void imx31_gpt_ic_irq_enable(struct icap_channel *ic)
> +{
> + struct imx_timer *imxtm = ic->imxtm;
> + unsigned int tmp;
> +
> + tmp = readl_relaxed(imxtm->base + V2_IR);
> + tmp |= ic->irqen_bit;
> + writel_relaxed(tmp, imxtm->base + V2_IR);
> +}
> +#define imx6dl_gpt_ic_irq_enable imx31_gpt_ic_irq_enable
> +
> +static void imx31_gpt_ic_irq_acknowledge(struct icap_channel *ic)
> +{
> + struct imx_timer *imxtm = ic->imxtm;
> +
> + writel_relaxed(ic->status_bit, imxtm->base + V2_TSTAT);
> +}
> +#define imx6dl_gpt_ic_irq_acknowledge imx31_gpt_ic_irq_acknowledge
> +
> +static bool imx1_gpt_is_ic_irq(struct icap_channel *ic, unsigned int tstat)
> +{
> + return false;
> +}
> +#define imx21_gpt_is_ic_irq imx1_gpt_is_ic_irq
> +
> +static bool imx31_gpt_is_ic_irq(struct icap_channel *ic, unsigned int tstat)
> +{
> + return (tstat & ic->status_bit) != 0;
> +}
> +#define imx6dl_gpt_is_ic_irq imx31_gpt_is_ic_irq
> +
> +static void imx31_gpt_ic_enable(struct icap_channel *ic, unsigned int mode)
> +{
> + struct imx_timer *imxtm = ic->imxtm;
> + unsigned int tctl, mask;
> +
> + mask = 0x3 << ic->mode_bit;
> + mode <<= ic->mode_bit;
> +
> + tctl = readl_relaxed(imxtm->base + MXC_TCTL);
> + tctl &= ~mask;
> + tctl |= mode;
> + writel_relaxed(tctl, imxtm->base + MXC_TCTL);
> +}
> +#define imx6dl_gpt_ic_enable imx31_gpt_ic_enable
> +
> +static void imx31_gpt_ic_disable(struct icap_channel *ic)
> +{
> + struct imx_timer *imxtm = ic->imxtm;
> + unsigned int tctl, mask;
> +
> + mask = 0x3 << ic->mode_bit;
> +
> + tctl = readl_relaxed(imxtm->base + MXC_TCTL);
> + tctl &= ~mask;
> + writel_relaxed(tctl, imxtm->base + MXC_TCTL);
> +}
> +#define imx6dl_gpt_ic_disable imx31_gpt_ic_disable
>
> static void __iomem *sched_clock_reg;
>
> @@ -149,6 +289,19 @@ static unsigned long imx_read_current_timer(void)
> }
> #endif
>
> +static u64 mxc_clocksource_read(struct clocksource *cs)
> +{
> + return mxc_read_sched_clock();
> +}
> +
> +static struct clocksource clocksource_mxc = {
> + .name = "mxc_timer1",
> + .rating = 200,
> + .mask = CLOCKSOURCE_MASK(32),
> + .read = mxc_clocksource_read,
> + .flags = CLOCK_SOURCE_IS_CONTINUOUS,
> +};
> +
> static int __init mxc_clocksource_init(struct imx_timer *imxtm)
> {
> unsigned int c = clk_get_rate(imxtm->clk_per);
> @@ -163,8 +316,7 @@ static int __init mxc_clocksource_init(struct imx_timer *imxtm)
> sched_clock_reg = reg;
>
> sched_clock_register(mxc_read_sched_clock, 32, c);
> - return clocksource_mmio_init(reg, "mxc_timer1", c, 200, 32,
> - clocksource_mmio_readl_up);
> + return clocksource_register_hz(&clocksource_mxc, c);
> }
>
> /* clock event */
> @@ -204,14 +356,14 @@ static int mxc_shutdown(struct clock_event_device *ced)
> u32 tcn;
>
> /* Disable interrupt in GPT module */
> - imxtm->gpt->gpt_irq_disable(imxtm);
> + imxtm->gpt->gpt_oc_irq_disable(imxtm);
>
> tcn = readl_relaxed(imxtm->base + imxtm->gpt->reg_tcn);
> /* Set event time into far-far future */
> writel_relaxed(tcn - 3, imxtm->base + imxtm->gpt->reg_tcmp);
>
> /* Clear pending interrupt */
> - imxtm->gpt->gpt_irq_acknowledge(imxtm);
> + imxtm->gpt->gpt_oc_irq_acknowledge(imxtm);
>
> #ifdef DEBUG
> printk(KERN_INFO "%s: changing mode\n", __func__);
> @@ -225,7 +377,7 @@ static int mxc_set_oneshot(struct clock_event_device *ced)
> struct imx_timer *imxtm = to_imx_timer(ced);
>
> /* Disable interrupt in GPT module */
> - imxtm->gpt->gpt_irq_disable(imxtm);
> + imxtm->gpt->gpt_oc_irq_disable(imxtm);
>
> if (!clockevent_state_oneshot(ced)) {
> u32 tcn = readl_relaxed(imxtm->base + imxtm->gpt->reg_tcn);
> @@ -233,7 +385,7 @@ static int mxc_set_oneshot(struct clock_event_device *ced)
> writel_relaxed(tcn - 3, imxtm->base + imxtm->gpt->reg_tcmp);
>
> /* Clear pending interrupt */
> - imxtm->gpt->gpt_irq_acknowledge(imxtm);
> + imxtm->gpt->gpt_oc_irq_acknowledge(imxtm);
> }
>
> #ifdef DEBUG
> @@ -246,7 +398,7 @@ static int mxc_set_oneshot(struct clock_event_device *ced)
> * to call mxc_set_next_event() or shutdown clock after
> * mode switching
> */
> - imxtm->gpt->gpt_irq_enable(imxtm);
> + imxtm->gpt->gpt_oc_irq_enable(imxtm);
>
> return 0;
> }
> @@ -259,12 +411,29 @@ static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id)
> struct clock_event_device *ced = dev_id;
> struct imx_timer *imxtm = to_imx_timer(ced);
> uint32_t tstat;
> + int i;
>
> tstat = readl_relaxed(imxtm->base + imxtm->gpt->reg_tstat);
>
> - imxtm->gpt->gpt_irq_acknowledge(imxtm);
> + for (i = 0; i < 2; i++) {
> + struct icap_channel *ic = &icap_channel[i];
> + ktime_t timestamp;
>
> - ced->event_handler(ced);
> + if (!imxtm->gpt->gpt_is_ic_irq(ic, tstat))
> + continue;
> +
> + imxtm->gpt->gpt_ic_irq_acknowledge(ic);
> +
> + timestamp = ns_to_ktime(timecounter_read(&ic->tc));
> +
> + if (ic->handler)
> + ic->handler(ic->chan, ic->dev_id, timestamp);
> + }
> +
> + if (imxtm->gpt->gpt_is_oc_irq(imxtm, tstat)) {
> + imxtm->gpt->gpt_oc_irq_acknowledge(imxtm);
> + ced->event_handler(ced);
> + }
>
> return IRQ_HANDLED;
> }
> @@ -289,16 +458,16 @@ static int __init mxc_clockevent_init(struct imx_timer *imxtm)
> IRQF_TIMER | IRQF_IRQPOLL, "i.MX Timer Tick", ced);
> }
>
> -static void imx1_gpt_setup_tctl(struct imx_timer *imxtm)
> +static void imx1_gpt_oc_setup_tctl(struct imx_timer *imxtm)
> {
> u32 tctl_val;
>
> tctl_val = MX1_2_TCTL_FRR | MX1_2_TCTL_CLK_PCLK1 | MXC_TCTL_TEN;
> writel_relaxed(tctl_val, imxtm->base + MXC_TCTL);
> }
> -#define imx21_gpt_setup_tctl imx1_gpt_setup_tctl
> +#define imx21_gpt_oc_setup_tctl imx1_gpt_oc_setup_tctl
>
> -static void imx31_gpt_setup_tctl(struct imx_timer *imxtm)
> +static void imx31_gpt_oc_setup_tctl(struct imx_timer *imxtm)
> {
> u32 tctl_val;
>
> @@ -311,7 +480,7 @@ static void imx31_gpt_setup_tctl(struct imx_timer *imxtm)
> writel_relaxed(tctl_val, imxtm->base + MXC_TCTL);
> }
>
> -static void imx6dl_gpt_setup_tctl(struct imx_timer *imxtm)
> +static void imx6dl_gpt_oc_setup_tctl(struct imx_timer *imxtm)
> {
> u32 tctl_val;
>
> @@ -332,10 +501,12 @@ static const struct imx_gpt_data imx1_gpt_data = {
> .reg_tstat = MX1_2_TSTAT,
> .reg_tcn = MX1_2_TCN,
> .reg_tcmp = MX1_2_TCMP,
> - .gpt_irq_enable = imx1_gpt_irq_enable,
> - .gpt_irq_disable = imx1_gpt_irq_disable,
> - .gpt_irq_acknowledge = imx1_gpt_irq_acknowledge,
> - .gpt_setup_tctl = imx1_gpt_setup_tctl,
> + .gpt_oc_irq_enable = imx1_gpt_oc_irq_enable,
> + .gpt_oc_irq_disable = imx1_gpt_oc_irq_disable,
> + .gpt_oc_irq_acknowledge = imx1_gpt_oc_irq_acknowledge,
> + .gpt_is_oc_irq = imx1_gpt_is_oc_irq,
> + .gpt_is_ic_irq = imx1_gpt_is_ic_irq,
> + .gpt_oc_setup_tctl = imx1_gpt_oc_setup_tctl,
> .set_next_event = mx1_2_set_next_event,
> };
>
> @@ -343,10 +514,12 @@ static const struct imx_gpt_data imx21_gpt_data = {
> .reg_tstat = MX1_2_TSTAT,
> .reg_tcn = MX1_2_TCN,
> .reg_tcmp = MX1_2_TCMP,
> - .gpt_irq_enable = imx21_gpt_irq_enable,
> - .gpt_irq_disable = imx21_gpt_irq_disable,
> - .gpt_irq_acknowledge = imx21_gpt_irq_acknowledge,
> - .gpt_setup_tctl = imx21_gpt_setup_tctl,
> + .gpt_oc_irq_enable = imx21_gpt_oc_irq_enable,
> + .gpt_oc_irq_disable = imx21_gpt_oc_irq_disable,
> + .gpt_oc_irq_acknowledge = imx21_gpt_oc_irq_acknowledge,
> + .gpt_is_oc_irq = imx21_gpt_is_oc_irq,
> + .gpt_is_ic_irq = imx21_gpt_is_ic_irq,
> + .gpt_oc_setup_tctl = imx21_gpt_oc_setup_tctl,
> .set_next_event = mx1_2_set_next_event,
> };
>
> @@ -354,27 +527,160 @@ static const struct imx_gpt_data imx31_gpt_data = {
> .reg_tstat = V2_TSTAT,
> .reg_tcn = V2_TCN,
> .reg_tcmp = V2_TCMP,
> - .gpt_irq_enable = imx31_gpt_irq_enable,
> - .gpt_irq_disable = imx31_gpt_irq_disable,
> - .gpt_irq_acknowledge = imx31_gpt_irq_acknowledge,
> - .gpt_setup_tctl = imx31_gpt_setup_tctl,
> + .gpt_oc_irq_enable = imx31_gpt_oc_irq_enable,
> + .gpt_oc_irq_disable = imx31_gpt_oc_irq_disable,
> + .gpt_oc_irq_acknowledge = imx31_gpt_oc_irq_acknowledge,
> + .gpt_is_oc_irq = imx31_gpt_is_oc_irq,
> + .gpt_oc_setup_tctl = imx31_gpt_oc_setup_tctl,
> .set_next_event = v2_set_next_event,
> +
> + /* input capture methods */
> + .gpt_ic_irq_enable = imx31_gpt_ic_irq_enable,
> + .gpt_ic_irq_disable = imx31_gpt_ic_irq_disable,
> + .gpt_ic_irq_acknowledge = imx31_gpt_ic_irq_acknowledge,
> + .gpt_is_ic_irq = imx31_gpt_is_ic_irq,
> + .gpt_ic_enable = imx31_gpt_ic_enable,
> + .gpt_ic_disable = imx31_gpt_ic_disable,
> };
>
> static const struct imx_gpt_data imx6dl_gpt_data = {
> .reg_tstat = V2_TSTAT,
> .reg_tcn = V2_TCN,
> .reg_tcmp = V2_TCMP,
> - .gpt_irq_enable = imx6dl_gpt_irq_enable,
> - .gpt_irq_disable = imx6dl_gpt_irq_disable,
> - .gpt_irq_acknowledge = imx6dl_gpt_irq_acknowledge,
> - .gpt_setup_tctl = imx6dl_gpt_setup_tctl,
> + .gpt_oc_irq_enable = imx6dl_gpt_oc_irq_enable,
> + .gpt_oc_irq_disable = imx6dl_gpt_oc_irq_disable,
> + .gpt_oc_irq_acknowledge = imx6dl_gpt_oc_irq_acknowledge,
> + .gpt_is_oc_irq = imx6dl_gpt_is_oc_irq,
> + .gpt_oc_setup_tctl = imx6dl_gpt_oc_setup_tctl,
> .set_next_event = v2_set_next_event,
> +
> + /* input capture methods */
> + .gpt_ic_irq_enable = imx6dl_gpt_ic_irq_enable,
> + .gpt_ic_irq_disable = imx6dl_gpt_ic_irq_disable,
> + .gpt_ic_irq_acknowledge = imx6dl_gpt_ic_irq_acknowledge,
> + .gpt_is_ic_irq = imx6dl_gpt_is_ic_irq,
> + .gpt_ic_enable = imx6dl_gpt_ic_enable,
> + .gpt_ic_disable = imx6dl_gpt_ic_disable,
> };
>
> +static u64 gpt_ic_read(const struct cyclecounter *cc)
> +{
> + struct icap_channel *ic = container_of(cc, struct icap_channel, cc);
> + struct imx_timer *imxtm = ic->imxtm;
> +
> + return readl_relaxed(imxtm->base + ic->cnt_reg);
> +}
> +
> +int mxc_request_input_capture(unsigned int chan, mxc_icap_handler_t handler,
> + unsigned long capflags, void *dev_id)
> +{
> + struct imx_timer *imxtm;
> + struct icap_channel *ic;
> + unsigned long flags;
> + u64 start_cycles;
> + int ret = 0;
> + u32 mode;
> +
> + /* we only care about rising and falling flags */
> + capflags &= (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING);
> +
> + if (chan > 1 || !handler || !capflags)
> + return -EINVAL;
> +
> + ic = &icap_channel[chan];
> + imxtm = ic->imxtm;
> +
> + if (!imxtm->gpt->gpt_ic_enable)
> + return -ENODEV;
> +
> + spin_lock_irqsave(&icap_lock, flags);
> +
> + if (ic->handler) {
> + ret = -EBUSY;
> + goto out;
> + }
> +
> + ic->handler = handler;
> + ic->dev_id = dev_id;
> +
> + switch (capflags) {
> + case IRQF_TRIGGER_RISING:
> + mode = V2_IM_RISING;
> + break;
> + case IRQF_TRIGGER_FALLING:
> + mode = V2_IM_FALLING;
> + break;
> + default:
> + mode = V2_IM_BOTH;
> + break;
> + }
> +
> + /* ack any pending input capture interrupt before enabling */
> + imxtm->gpt->gpt_ic_irq_acknowledge(ic);
> +
> + /*
> + * initialize the cyclecounter. The input capture is capturing
> + * from the mxc clocksource, so it has the same mask/shift/mult.
> + */
> + memset(&ic->cc, 0, sizeof(ic->cc));
> + ic->cc.read = gpt_ic_read;
> + ic->cc.mask = clocksource_mxc.mask;
> + ic->cc.shift = clocksource_mxc.shift;
> + ic->cc.mult = clocksource_mxc.mult;
> +
> + /* initialize a timecounter for the input capture */
> + start_cycles = mxc_read_sched_clock();
> + timecounter_init(&ic->tc, &ic->cc, ktime_get_ns());
> + /*
> + * timecounter_init() read the last captured timer count, but
> + * that's not the start cycle counter, so update it with the
> + * real start cycles.
> + */
> + ic->tc.cycle_last = start_cycles;
> +
> + imxtm->gpt->gpt_ic_enable(ic, mode);
> + imxtm->gpt->gpt_ic_irq_enable(ic);
> +
> +out:
> + spin_unlock_irqrestore(&icap_lock, flags);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(mxc_request_input_capture);
> +
> +void mxc_free_input_capture(unsigned int chan, void *dev_id)
> +{
> + struct imx_timer *imxtm;
> + struct icap_channel *ic;
> + unsigned long flags;
> +
> + if (chan > 1)
> + return;
> +
> + ic = &icap_channel[chan];
> + imxtm = ic->imxtm;
> +
> + if (!imxtm->gpt->gpt_ic_disable)
> + return;
> +
> + spin_lock_irqsave(&icap_lock, flags);
> +
> + if (!ic->handler || dev_id != ic->dev_id)
> + goto out;
> +
> + imxtm->gpt->gpt_ic_irq_disable(ic);
> + imxtm->gpt->gpt_ic_disable(ic);
> +
> + ic->handler = NULL;
> + ic->dev_id = NULL;
> +out:
> + spin_unlock_irqrestore(&icap_lock, flags);
> +}
> +EXPORT_SYMBOL_GPL(mxc_free_input_capture);
> +
> static int __init _mxc_timer_init(struct imx_timer *imxtm)
> {
> - int ret;
> + struct icap_channel *ic;
> + int i, ret;
>
> switch (imxtm->type) {
> case GPT_TYPE_IMX1:
> @@ -410,14 +716,23 @@ static int __init _mxc_timer_init(struct imx_timer *imxtm)
> writel_relaxed(0, imxtm->base + MXC_TCTL);
> writel_relaxed(0, imxtm->base + MXC_TPRER); /* see datasheet note */
>
> - imxtm->gpt->gpt_setup_tctl(imxtm);
> + imxtm->gpt->gpt_oc_setup_tctl(imxtm);
>
> /* init and register the timer to the framework */
> ret = mxc_clocksource_init(imxtm);
> if (ret)
> return ret;
>
> - return mxc_clockevent_init(imxtm);
> + ret = mxc_clockevent_init(imxtm);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < 2; i++) {
> + ic = &icap_channel[i];
> + ic->imxtm = imxtm;
> + }
> +
> + return 0;
> }
>
> void __init mxc_timer_init(unsigned long pbase, int irq, enum imx_gpt_type type)
> @@ -439,6 +754,70 @@ void __init mxc_timer_init(unsigned long pbase, int irq, enum imx_gpt_type type)
> _mxc_timer_init(imxtm);
> }
>
> +/*
> + * a platform driver is needed in order to acquire pinmux
> + * for input capture pins. The probe call is also useful
> + * for setting up the input capture channel structures.
> + */
> +static int mxc_timer_probe(struct platform_device *pdev)
> +{
> + struct icap_channel *ic;
> + int i;
> +
> + /* setup the input capture channels */
> + for (i = 0; i < 2; i++) {
> + ic = &icap_channel[i];
> + ic->chan = i;
> + if (i == 0) {
> + ic->cnt_reg = V2_TCAP1;
> + ic->irqen_bit = V2_IR_IF1;
> + ic->status_bit = V2_TSTAT_IF1;
> + ic->mode_bit = V2_TCTL_IM1_BIT;
> + } else {
> + ic->cnt_reg = V2_TCAP2;
> + ic->irqen_bit = V2_IR_IF2;
> + ic->status_bit = V2_TSTAT_IF2;
> + ic->mode_bit = V2_TCTL_IM2_BIT;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int mxc_timer_remove(struct platform_device *pdev)
> +{
> + return 0;
> +}
> +
> +static const struct of_device_id timer_of_match[] = {
> + { .compatible = "fsl,imx1-gpt" },
> + { .compatible = "fsl,imx21-gpt" },
> + { .compatible = "fsl,imx27-gpt" },
> + { .compatible = "fsl,imx31-gpt" },
> + { .compatible = "fsl,imx25-gpt" },
> + { .compatible = "fsl,imx50-gpt" },
> + { .compatible = "fsl,imx51-gpt" },
> + { .compatible = "fsl,imx53-gpt" },
> + { .compatible = "fsl,imx6q-gpt" },
> + { .compatible = "fsl,imx6dl-gpt" },
> + { .compatible = "fsl,imx6sl-gpt" },
> + { .compatible = "fsl,imx6sx-gpt" },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, timer_of_match);
> +
> +static struct platform_driver mxc_timer_pdrv = {
> + .probe = mxc_timer_probe,
> + .remove = mxc_timer_remove,
> + .driver = {
> + .name = "mxc-timer",
> + .owner = THIS_MODULE,
> + .of_match_table = timer_of_match,
> + },
> +};
> +
> +module_platform_driver(mxc_timer_pdrv);
> +
> static int __init mxc_timer_init_dt(struct device_node *np, enum imx_gpt_type type)
> {
> struct imx_timer *imxtm;
> diff --git a/include/linux/mxc_icap.h b/include/linux/mxc_icap.h
> new file mode 100644
> index 000000000000..fa5ffdf3b589
> --- /dev/null
> +++ b/include/linux/mxc_icap.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * i.MX GPT Input Capture support.
> + *
> + * Copyright (C) 2015 Mentor Graphics, Inc. All Rights Reserved.
> + */
> +#ifndef __MXC_ICAP_H__
> +#define __MXC_ICAP_H__
> +
> +typedef void (*mxc_icap_handler_t)(int, void *, ktime_t);
> +
> +int mxc_request_input_capture(unsigned int chan, mxc_icap_handler_t handler,
> + unsigned long capflags, void *dev_id);
> +void mxc_free_input_capture(unsigned int chan, void *dev_id);
> +
> +#endif


--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |