mirror of https://github.com/OpenIPC/firmware.git
310 lines
9.6 KiB
Diff
310 lines
9.6 KiB
Diff
diff -drupN a/drivers/clocksource/ingenic_sysost.c b/drivers/clocksource/ingenic_sysost.c
|
|
--- a/drivers/clocksource/ingenic_sysost.c 1970-01-01 03:00:00.000000000 +0300
|
|
+++ b/drivers/clocksource/ingenic_sysost.c 2022-06-09 05:02:28.000000000 +0300
|
|
@@ -0,0 +1,305 @@
|
|
+/*
|
|
+ * Copyright (C) 2015 Ingenic Semiconductor Co., Ltd.
|
|
+ * Author: xyfu <xiaoyang.fu@ingenic.com> (kernel.3.10.14)
|
|
+ * Modified: cli <chen.li@ingenic.com>
|
|
+ *
|
|
+ * Operating System Timer interface for Ingenic's SOC, such as X1000,
|
|
+ * and so on. (kernel.4.4)
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+/* #define DEBUG */
|
|
+/* #define VERBOSE_DEBUG */
|
|
+#include <linux/types.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/clockchips.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/sched_clock.h>
|
|
+#include <linux/clk-provider.h>
|
|
+#include <linux/notifier.h>
|
|
+#include <linux/cpu.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/of_irq.h>
|
|
+#include <linux/delay.h>
|
|
+#include <soc/ost.h>
|
|
+
|
|
+#define CLKSOURCE_DIV (16)
|
|
+#define CLKEVENT_DIV (16)
|
|
+
|
|
+/*-----------------------------------------------------------------------------
|
|
+ * C L O C K S O U R C E
|
|
+ *-----------------------------------------------------------------------------*/
|
|
+struct tmr_src {
|
|
+ struct clocksource cs;
|
|
+ struct clk *clk_gate;
|
|
+ void __iomem *iobase;
|
|
+};
|
|
+
|
|
+static cycle_t ingenic_read_cycles(struct clocksource *cs)
|
|
+{
|
|
+ struct tmr_src *tmr = container_of(cs, struct tmr_src, cs);
|
|
+ union clycle_type
|
|
+ {
|
|
+ cycle_t cycle64;
|
|
+ unsigned int cycle32[2];
|
|
+ } cycle;
|
|
+
|
|
+ cycle.cycle32[0] = ost_readl(tmr->iobase + OST_T2CNTL);
|
|
+ cycle.cycle32[1] = ost_readl(tmr->iobase + OST_TCNT2HBUF);
|
|
+
|
|
+ return cycle.cycle64;
|
|
+}
|
|
+
|
|
+static int tmr_src_enable(struct clocksource *cs)
|
|
+{
|
|
+ struct tmr_src *tmr = container_of(cs,struct tmr_src,cs);
|
|
+
|
|
+ ost_writel(tmr->iobase + OST_TESR, TESR_OSTEN2);
|
|
+ return 0;
|
|
+}
|
|
+static void tmr_src_disable(struct clocksource *cs)
|
|
+{
|
|
+ struct tmr_src *tmr = container_of(cs,struct tmr_src,cs);
|
|
+
|
|
+ ost_writel(tmr->iobase + OST_TCR, TCR_OSTCLR2);
|
|
+ ost_writel(tmr->iobase + OST_TECR, TESR_OSTEN2);
|
|
+}
|
|
+static void tmr_src_suspend(struct clocksource *cs)
|
|
+{
|
|
+ struct tmr_src *tmr = container_of(cs, struct tmr_src, cs);
|
|
+
|
|
+ ost_writel(tmr->iobase + OST_TFR , ~TFR_OSTM);
|
|
+ if (tmr->clk_gate) {
|
|
+ clk_disable_unprepare(tmr->clk_gate);
|
|
+
|
|
+ }
|
|
+}
|
|
+static void tmr_src_resume(struct clocksource *cs)
|
|
+{
|
|
+ struct tmr_src *tmr = container_of(cs, struct tmr_src, cs);
|
|
+ if (tmr->clk_gate)
|
|
+ clk_prepare_enable(tmr->clk_gate);
|
|
+}
|
|
+
|
|
+static struct tmr_src tmr_src = {
|
|
+ .cs = {
|
|
+ .name = "ingenic_clocksource",
|
|
+ .rating = 400,
|
|
+ .read = ingenic_read_cycles,
|
|
+ .mask = CLOCKSOURCE_MASK(64),
|
|
+ .flags = CLOCK_SOURCE_WATCHDOG | CLOCK_SOURCE_IS_CONTINUOUS,
|
|
+ .enable = tmr_src_enable,
|
|
+ .disable = tmr_src_disable,
|
|
+ .suspend = tmr_src_suspend,
|
|
+ .resume = tmr_src_resume,
|
|
+ }
|
|
+};
|
|
+
|
|
+static u64 notrace ingenic_read_sched_clock(void)
|
|
+{
|
|
+ union clycle_type {
|
|
+ uint64_t cycle64;
|
|
+ uint32_t cycle32[2];
|
|
+ } cycle;
|
|
+ cycle.cycle32[0] = ost_readl(tmr_src.iobase + OST_T2CNTL);
|
|
+ cycle.cycle32[1] = ost_readl(tmr_src.iobase + OST_TCNT2HBUF);
|
|
+
|
|
+ return cycle.cycle64;
|
|
+}
|
|
+
|
|
+static void __init ingenic_clocksource_init(struct tmr_src *tmr, unsigned long ext_rate)
|
|
+{
|
|
+ unsigned long hz = ext_rate / CLKSOURCE_DIV;
|
|
+ unsigned int val;
|
|
+
|
|
+ tmr->clk_gate = clk_get(NULL, "gate_ost");
|
|
+ if (IS_ERR_OR_NULL(tmr->clk_gate)) {
|
|
+ pr_err("ERROR: Failed to get clk, Please check clk driver.\n");
|
|
+ pr_err("%s, incase of Debug at the very beginning, we ignore the clk_gate. \n \
|
|
+ You must implemented clk driver immediately\n\n\t\n", __func__);
|
|
+ } else {
|
|
+ clk_prepare_enable(tmr->clk_gate);
|
|
+ }
|
|
+
|
|
+ val = ost_readl(tmr->iobase + OST_TCCR);
|
|
+ if (ost_readl(tmr->iobase + OST_TER) & TESR_OSTEN2) {
|
|
+ u32 div = 0;
|
|
+ /*
|
|
+ * get previous boot time
|
|
+ */
|
|
+ switch ((val & TCCRDIV_MSK2) >> TCCRDIV_SFT2) {
|
|
+ case 0: div = 1; break;
|
|
+ case 1: div = 4; break;
|
|
+ case 2: div = 16; break;
|
|
+ }
|
|
+ if (likely(div)) {
|
|
+ u64 cycles = ingenic_read_sched_clock();
|
|
+ u32 pre_hz = ext_rate / div;
|
|
+ cycles = cycles * USEC_PER_SEC;
|
|
+ do_div(cycles, pre_hz);
|
|
+ pr_info("Previous Boot Time is %u us\n", (u32)cycles);
|
|
+ }
|
|
+ ost_writel(tmr->iobase + OST_TECR, TESR_OSTEN2);
|
|
+ }
|
|
+ val &= ~TCCRDIV_MSK2;
|
|
+ val |= TCCRDIV2(CLKSOURCE_DIV);
|
|
+ ost_writel(tmr->iobase + OST_TCCR, val);
|
|
+ ost_writel(tmr->iobase + OST_TCR, TCR_OSTCLR2);
|
|
+ ost_writel(tmr->iobase + OST_TESR, TESR_OSTEN2);
|
|
+
|
|
+ clocksource_register_hz(&tmr->cs, hz);
|
|
+ sched_clock_register(ingenic_read_sched_clock, 64, hz);
|
|
+}
|
|
+
|
|
+/*-----------------------------------------------------------------------------
|
|
+ * C L O C K E V E N T
|
|
+ *-----------------------------------------------------------------------------*/
|
|
+struct ingenic_timerevent {
|
|
+ struct clock_event_device clkevt;
|
|
+ void __iomem *iobase;
|
|
+ unsigned int periodic_ticks;
|
|
+ struct clk *clk_gate;
|
|
+ unsigned int rate;
|
|
+ int irq;
|
|
+ struct irqaction evt_action;
|
|
+} ingenic_clockevent;
|
|
+
|
|
+
|
|
+static int ingenic_set_state_periodic(struct clock_event_device *cd)
|
|
+{
|
|
+ struct ingenic_timerevent *evt_dev = container_of(cd,
|
|
+ struct ingenic_timerevent, clkevt);
|
|
+
|
|
+ ost_writel(evt_dev->iobase + OST_TMR , TMR_OSTM);
|
|
+ ost_writel(evt_dev->iobase + OST_TECR , TESR_OSTEN1);
|
|
+ ost_writel(evt_dev->iobase + OST_TFR , ~TFR_OSTM);
|
|
+ ost_writel(evt_dev->iobase + OST_T1DFR, evt_dev->periodic_ticks);
|
|
+ ost_writel(evt_dev->iobase + OST_TCR , TCR_OSTCLR1);
|
|
+ ost_writel(evt_dev->iobase + OST_TESR , TESR_OSTEN1);
|
|
+ ost_writel(evt_dev->iobase + OST_TMR , ~TMR_OSTM);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ingenic_set_state_shutdown(struct clock_event_device *cd)
|
|
+{
|
|
+ struct ingenic_timerevent *evt_dev = container_of(cd,
|
|
+ struct ingenic_timerevent, clkevt);
|
|
+ ost_writel(evt_dev->iobase + OST_TECR , TESR_OSTEN1);
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+static int ingenic_set_next_event(unsigned long evt, struct clock_event_device *cd)
|
|
+{
|
|
+ struct ingenic_timerevent *evt_dev = container_of(cd,
|
|
+ struct ingenic_timerevent, clkevt);
|
|
+
|
|
+ ost_writel(evt_dev->iobase + OST_TMR , TMR_OSTM);
|
|
+ ost_writel(evt_dev->iobase + OST_TECR , TESR_OSTEN1);
|
|
+ ost_writel(evt_dev->iobase + OST_TFR , ~TFR_OSTM);
|
|
+ ost_writel(evt_dev->iobase + OST_T1DFR, evt);
|
|
+ ost_writel(evt_dev->iobase + OST_TCR , TCR_OSTCLR1);
|
|
+ ost_writel(evt_dev->iobase + OST_TESR , TESR_OSTEN1);
|
|
+ ost_writel(evt_dev->iobase + OST_TMR , ~TMR_OSTM);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static irqreturn_t ingenic_timer_interrupt(int irq, void *dev_id)
|
|
+{
|
|
+ struct ingenic_timerevent *evt_dev = dev_id;
|
|
+ struct clock_event_device *cd = &evt_dev->clkevt;
|
|
+
|
|
+ ost_writel(evt_dev->iobase + OST_TFR, ~TFR_OSTM);
|
|
+
|
|
+ if (clockevent_state_oneshot(cd))
|
|
+ ost_writel(evt_dev->iobase + OST_TECR , TESR_OSTEN1);
|
|
+
|
|
+ evt_dev->clkevt.event_handler(&evt_dev->clkevt);
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+static void __init ingenic_clockevent_init(struct ingenic_timerevent *evt_dev, unsigned long ext_rate)
|
|
+{
|
|
+ struct clock_event_device *cd = &evt_dev->clkevt;
|
|
+ unsigned int val;
|
|
+
|
|
+ ost_writel(evt_dev->iobase + OST_TMR, TMR_OSTM);
|
|
+ ost_writel(evt_dev->iobase + OST_TECR, TESR_OSTEN1);
|
|
+ ost_writel(evt_dev->iobase + OST_TFR, ~TFR_OSTM);
|
|
+ val = ost_readl(evt_dev->iobase + OST_TCCR);
|
|
+ val &= ~TCCRDIV_MSK1;
|
|
+ val |= TCCRDIV1(CLKEVENT_DIV);
|
|
+ ost_writel(evt_dev->iobase + OST_TCCR, val);
|
|
+ ost_writel(evt_dev->iobase + OST_TCR, TCR_OSTCLR1);
|
|
+
|
|
+ evt_dev->evt_action.handler = ingenic_timer_interrupt;
|
|
+ evt_dev->evt_action.flags = IRQF_TIMER;
|
|
+ evt_dev->evt_action.name = "ingenic-timerost";
|
|
+ evt_dev->evt_action.dev_id = (void*)evt_dev;
|
|
+ evt_dev->rate = ext_rate / CLKEVENT_DIV;
|
|
+ evt_dev->periodic_ticks = ((evt_dev->rate + (HZ >> 1)) / HZ) - 1; /* - 1 for scroll back */
|
|
+
|
|
+ if (setup_irq(evt_dev->irq, &evt_dev->evt_action) < 0)
|
|
+ panic("timer request ost error\n");
|
|
+
|
|
+ memset(cd, 0, sizeof(struct clock_event_device));
|
|
+ cd->name = "ingenic-clockenvent";
|
|
+ cd->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC;
|
|
+ cd->rating = 400;
|
|
+ cd->set_state_periodic = ingenic_set_state_periodic;
|
|
+ cd->set_state_oneshot = ingenic_set_state_shutdown;
|
|
+ cd->set_state_shutdown = ingenic_set_state_shutdown;
|
|
+ cd->set_next_event = ingenic_set_next_event;
|
|
+ cd->irq = evt_dev->irq;
|
|
+ cd->cpumask = cpumask_of(0);
|
|
+
|
|
+ clockevents_config_and_register(cd, evt_dev->rate, 4, 0xffffffff);
|
|
+}
|
|
+
|
|
+static void __init ingenic_ost_init(struct device_node *np)
|
|
+{
|
|
+ struct ingenic_timerevent *evt = &ingenic_clockevent;
|
|
+ struct tmr_src *tmr = &tmr_src ;
|
|
+ struct clk *ext_clk = NULL;
|
|
+ unsigned long ext_rate;
|
|
+ void __iomem *iobase = NULL;
|
|
+ int irq_ost = -1;
|
|
+
|
|
+ iobase = of_io_request_and_map(np, 0, "ost");
|
|
+ if(iobase == NULL) {
|
|
+ pr_err("Failed to map clocksource iobase!\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ irq_ost = of_irq_get_byname(np, "sys_ost");
|
|
+ if (irq_ost < 0) {
|
|
+ pr_err("ftm: unable to get IRQ from DT, %d\n", irq_ost);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ext_clk = clk_get(NULL, "ext");
|
|
+ if (IS_ERR_OR_NULL(ext_clk)) {
|
|
+ pr_warn("Warning Ingenic Ost: Can not get extern clock, Please check clk driver !!\n\n\t\n");
|
|
+ ext_rate = 24000000;
|
|
+ } else {
|
|
+
|
|
+ ext_rate = clk_get_rate(ext_clk);
|
|
+ clk_put(ext_clk);
|
|
+ }
|
|
+
|
|
+ tmr->iobase = iobase;
|
|
+ evt->iobase = iobase;
|
|
+ evt->irq = irq_ost;
|
|
+
|
|
+ ingenic_clocksource_init(tmr, ext_rate);
|
|
+ ingenic_clockevent_init(evt, ext_rate);
|
|
+}
|
|
+
|
|
+CLOCKSOURCE_OF_DECLARE(x1000_ost_init, "ingenic,x1000-ost", ingenic_ost_init);
|
|
+CLOCKSOURCE_OF_DECLARE(x1800_ost_init, "ingenic,x1800-ost", ingenic_ost_init);
|
|
+CLOCKSOURCE_OF_DECLARE(t40_ost_init, "ingenic,t40-ost", ingenic_ost_init);
|