firmware/br-ext-chip-ingenic/board/t40/kernel/patches/00000-drivers_clocksource_i...

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);