From patchwork Tue May 31 09:49:57 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Neil Armstrong X-Patchwork-Id: 9144107 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id C569D60757 for ; Tue, 31 May 2016 09:52:09 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B64DA208C2 for ; Tue, 31 May 2016 09:52:09 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id AAE3527BF1; Tue, 31 May 2016 09:52:09 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.1 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED, T_DKIM_INVALID autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 99841208C2 for ; Tue, 31 May 2016 09:52:08 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1b7gJz-0005fL-4o; Tue, 31 May 2016 09:50:47 +0000 Received: from mail-wm0-x22c.google.com ([2a00:1450:400c:c09::22c]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1b7gJh-0005Yp-4a for linux-arm-kernel@lists.infradead.org; Tue, 31 May 2016 09:50:31 +0000 Received: by mail-wm0-x22c.google.com with SMTP id z87so100134960wmh.0 for ; Tue, 31 May 2016 02:50:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=baylibre-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=3+wyj5TfVeP0+DoCzel3dCZUFkeYHbky7pqxKofvUZs=; b=wA9Ug7FRq1duKf0TZLXATO+cWGAYMkAHd9+IBdvr7dHBnCir+to5FF6Reb3fbQFo61 Zr02l2rxCUECxHTZgua+i5/WScsgzbAKxo6q726LauJ5MwuyZFWvOX18F+Y1OXnyYLrJ TJMpjxc0Er0kN8SrvkbMdqNsX+ywdgi9GfDqMzU51Tv0yTkqVZxGD9DM6B+RCp2uL29s muw0L6hPxzbEdtQLWvsly+agZdL+eHEaaBDy4MyXYBTVin2afxf1/SF+3FOa+LJcUpuw KGc+i8V+HrLCP/W0XhNn1lER3WSFDazV8qoquxPdbSuBhJdOdmXgduFg+PSKLC37j5q8 dQeQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=3+wyj5TfVeP0+DoCzel3dCZUFkeYHbky7pqxKofvUZs=; b=fWga+RP4Kmot2qVpV3QUV3H4mY4k/OavMgekYtj7hQgcAb++04OPRr5ngKqwWkqGI8 XsFLRj8SuWgkzHEJql5enQi/GLnHCob7MvXqTHX7LG+CTkmMvd1cXtsU/zi7jkFjFB5K YmEseyobDJH4PDJBZYUvEoOY83dXjKFYHHzrDKxCEFkz11gMlOqSY3vUfWwRhPFSKcpt fltBDmg6B4R663xRjdfrri1bs92V00fJkb5Y5pkryKynJlVxnH5wh8a9wMF6tMKyxpI5 YjY7Hwr3WM09wgiA0jJq742yzKemjmSPgaUZg3/iJUOPQuZk9WfHMQf7qv9xebM+FHHs azuA== X-Gm-Message-State: ALyK8tKsWUXATwQJ3TO66W+r7iWAT28Wy1jbvfvN1x32JdGwdb1xa4X8Elqvmg0yPzpsnqfB X-Received: by 10.194.233.5 with SMTP id ts5mr33975198wjc.109.1464688207301; Tue, 31 May 2016 02:50:07 -0700 (PDT) Received: from localhost.localdomain ([90.63.244.31]) by smtp.gmail.com with ESMTPSA id g129sm28142973wme.1.2016.05.31.02.50.06 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 31 May 2016 02:50:06 -0700 (PDT) From: Neil Armstrong To: linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, daniel.lezcano@linaro.org, tglx@linutronix.de Subject: [PATCH 1/2] clocksource: Add Oxford Semiconductor RPS Dual Timer Date: Tue, 31 May 2016 11:49:57 +0200 Message-Id: <1464688198-9655-2-git-send-email-narmstrong@baylibre.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1464688198-9655-1-git-send-email-narmstrong@baylibre.com> References: <1464688198-9655-1-git-send-email-narmstrong@baylibre.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20160531_025029_473569_0E32DCA1 X-CRM114-Status: GOOD ( 22.41 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Ma Haijun , Neil Armstrong MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP Add clocksource and clockevent driver from dual RPS timer. The HW provides a dual one-shot or periodic 24bit timers, the drivers set the first one as tick event source and the second as a continuous scheduler clock source. The timer can use 1, 16 or 256 as pre-dividers, thus the clocksource uses 16 by default. CC: Ma Haijun Signed-off-by: Neil Armstrong --- drivers/clocksource/Kconfig | 6 + drivers/clocksource/Makefile | 1 + drivers/clocksource/timer-oxnas-rps.c | 273 ++++++++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 drivers/clocksource/timer-oxnas-rps.c diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 47352d2..7e382c5 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -293,6 +293,12 @@ config VF_PIT_TIMER help Support for Period Interrupt Timer on Freescale Vybrid Family SoCs. +config OXNAS_RPS_TIMER + bool + select CLKSRC_MMIO + help + This enables support for the Oxford Semiconductor OXNAS RPS timers. + config SYS_SUPPORTS_SH_CMT bool diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 473974f..bc66981 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_MTK_TIMER) += mtk_timer.o obj-$(CONFIG_CLKSRC_PISTACHIO) += time-pistachio.o obj-$(CONFIG_CLKSRC_TI_32K) += timer-ti-32k.o obj-$(CONFIG_CLKSRC_NPS) += timer-nps.o +obj-$(CONFIG_OXNAS_RPS_TIMER) += timer-oxnas-rps.o obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o obj-$(CONFIG_ARM_GLOBAL_TIMER) += arm_global_timer.o diff --git a/drivers/clocksource/timer-oxnas-rps.c b/drivers/clocksource/timer-oxnas-rps.c new file mode 100644 index 0000000..f42f73d --- /dev/null +++ b/drivers/clocksource/timer-oxnas-rps.c @@ -0,0 +1,273 @@ +/* + * drivers/clocksource/timer-oxnas-rps.c + * + * Copyright (C) 2009 Oxford Semiconductor Ltd + * Copyright (C) 2013 Ma Haijun + * Copyright (C) 2016 Neil Armstrong + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see . + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TIMER1 used as tick + * TIMER2 used as clocksource + */ + +/* Registers definitions */ + +#define TIMER_LOAD_REG 0x0 +#define TIMER_CURR_REG 0x4 +#define TIMER_CTRL_REG 0x8 +#define TIMER_CLRINT_REG 0xC + +#define TIMER_BITS 24 + +#define TIMER_MAX_VAL (BIT(TIMER_BITS) - 1) + +#define TIMER_PERIODIC BIT(6) +#define TIMER_ENABLE BIT(7) + +#define TIMER_DIV1 (0) +#define TIMER_DIV16 (1 << 2) +#define TIMER_DIV256 (2 << 2) + +#define TIMER1_REG_OFFSET 0 +#define TIMER2_REG_OFFSET 0x20 + +/* Clockevent & Clocksource data */ + +struct oxnas_rps_timer { + struct clock_event_device clkevent; + void __iomem *clksrc_base; + void __iomem *clkevt_base; + unsigned long timer_period; + unsigned int timer_prescaler; + struct clk *clk; + int irq; +}; + +static irqreturn_t oxnas_rps_timer_irq(int irq, void *dev_id) +{ + struct oxnas_rps_timer *rps = dev_id; + + writel_relaxed(0, rps->clkevt_base + TIMER_CLRINT_REG); + + rps->clkevent.event_handler(&rps->clkevent); + + return IRQ_HANDLED; +} + +static void oxnas_rps_timer_config(struct oxnas_rps_timer *rps, + unsigned long period, + unsigned int periodic) +{ + uint32_t cfg = rps->timer_prescaler; + + if (period) + cfg |= TIMER_ENABLE; + + if (periodic) + cfg |= TIMER_PERIODIC; + + writel_relaxed(period, rps->clkevt_base + TIMER_LOAD_REG); + writel_relaxed(cfg, rps->clkevt_base + TIMER_CTRL_REG); +} + +static int oxnas_rps_timer_shutdown(struct clock_event_device *evt) +{ + struct oxnas_rps_timer *rps = + container_of(evt, struct oxnas_rps_timer, clkevent); + + if (!clockevent_state_periodic(evt)) + return 0; + + oxnas_rps_timer_config(rps, 0, 0); + + return 0; +} + +static int oxnas_rps_timer_set_periodic(struct clock_event_device *evt) +{ + struct oxnas_rps_timer *rps = + container_of(evt, struct oxnas_rps_timer, clkevent); + + oxnas_rps_timer_config(rps, rps->timer_period, 1); + + return 0; +} + +static int oxnas_rps_timer_set_oneshot(struct clock_event_device *evt) +{ + struct oxnas_rps_timer *rps = + container_of(evt, struct oxnas_rps_timer, clkevent); + + oxnas_rps_timer_config(rps, rps->timer_period, 0); + + return 0; +} + +static int oxnas_rps_timer_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + struct oxnas_rps_timer *rps = + container_of(evt, struct oxnas_rps_timer, clkevent); + + oxnas_rps_timer_config(rps, delta, 0); + + return 0; +} + +static void __init oxnas_rps_clockevent_init(struct oxnas_rps_timer *rps) +{ + ulong clk_rate = clk_get_rate(rps->clk); + ulong timer_rate; + + /* Start with prescaler 1 */ + rps->timer_prescaler = TIMER_DIV1; + rps->timer_period = DIV_ROUND_UP(clk_rate, HZ); + timer_rate = clk_rate; + + if (rps->timer_period > TIMER_MAX_VAL) { + rps->timer_prescaler = TIMER_DIV16; + rps->timer_period = DIV_ROUND_UP(clk_rate / 16, HZ); + timer_rate = clk_rate / 16; + } + if (rps->timer_period > TIMER_MAX_VAL) { + rps->timer_prescaler = TIMER_DIV256; + rps->timer_period = DIV_ROUND_UP(clk_rate / 256, HZ); + timer_rate = clk_rate / 256; + } + + rps->clkevent.name = "oxnas-rps"; + rps->clkevent.features = CLOCK_EVT_FEAT_PERIODIC | + CLOCK_EVT_FEAT_ONESHOT | + CLOCK_EVT_FEAT_DYNIRQ; + rps->clkevent.tick_resume = oxnas_rps_timer_shutdown; + rps->clkevent.set_state_shutdown = oxnas_rps_timer_shutdown; + rps->clkevent.set_state_periodic = oxnas_rps_timer_set_periodic; + rps->clkevent.set_state_oneshot = oxnas_rps_timer_set_oneshot; + rps->clkevent.set_next_event = oxnas_rps_timer_next_event; + rps->clkevent.rating = 200; + rps->clkevent.cpumask = cpu_possible_mask; + rps->clkevent.irq = rps->irq; + clockevents_config_and_register(&rps->clkevent, + timer_rate, + 1, + TIMER_MAX_VAL); + + pr_info("Registered clock event rate %luHz prescaler %d period %lu\n", + clk_rate, + rps->timer_prescaler, + rps->timer_period); +} + +/* Clocksource */ + +static void __iomem *timer_sched_base; + +static u64 notrace oxnas_rps_read_sched_clock(void) +{ + return ~readl_relaxed(timer_sched_base); +} + +static void __init oxnas_rps_clocksource_init(struct oxnas_rps_timer *rps) +{ + ulong clk_rate = clk_get_rate(rps->clk); + int ret; + + /* use prescale 16 */ + clk_rate = clk_rate / 16; + + writel_relaxed(TIMER_MAX_VAL, rps->clksrc_base + TIMER_LOAD_REG); + writel_relaxed(TIMER_PERIODIC | TIMER_ENABLE | TIMER_DIV16, + rps->clksrc_base + TIMER_CTRL_REG); + + timer_sched_base = rps->clksrc_base + TIMER_CURR_REG; + sched_clock_register(oxnas_rps_read_sched_clock, + TIMER_BITS, clk_rate); + ret = clocksource_mmio_init(timer_sched_base, + "oxnas_rps_clocksource_timer", + clk_rate, 250, TIMER_BITS, + clocksource_mmio_readl_down); + if (WARN_ON(ret)) + pr_err("can't register clocksource\n"); + + pr_info("Registered clocksource rate %luHz\n", clk_rate); +} + +static void __init oxnas_rps_timer_init(struct device_node *np) +{ + struct oxnas_rps_timer *rps; + void __iomem *base; + int ret; + + rps = kzalloc(sizeof(*rps), GFP_KERNEL); + if (WARN_ON(!rps)) + return; + + rps->clk = of_clk_get(np, 0); + if (WARN_ON(IS_ERR(rps->clk))) + return; + + if (WARN_ON(clk_prepare_enable(rps->clk))) + goto err; + + base = of_iomap(np, 0); + if (WARN_ON(!base)) + goto err; + + + rps->irq = irq_of_parse_and_map(np, 0); + if (WARN_ON(rps->irq < 0)) + goto err; + + rps->clkevt_base = base + TIMER1_REG_OFFSET; + rps->clksrc_base = base + TIMER2_REG_OFFSET; + + /* Disable timers */ + writel_relaxed(0, rps->clkevt_base + TIMER_CTRL_REG); + writel_relaxed(0, rps->clksrc_base + TIMER_CTRL_REG); + writel_relaxed(0, rps->clkevt_base + TIMER_LOAD_REG); + writel_relaxed(0, rps->clksrc_base + TIMER_LOAD_REG); + writel_relaxed(0, rps->clkevt_base + TIMER_CLRINT_REG); + writel_relaxed(0, rps->clksrc_base + TIMER_CLRINT_REG); + + ret = request_irq(rps->irq, oxnas_rps_timer_irq, + IRQF_TIMER | IRQF_IRQPOLL, + "rps-timer", rps); + if (WARN_ON(ret)) + goto err; + + oxnas_rps_clockevent_init(rps); + oxnas_rps_clocksource_init(rps); + + return; +err: + clk_put(rps->clk); + kfree(rps); +} + +CLOCKSOURCE_OF_DECLARE(ox810se_rps, + "oxsemi,ox810se-rps-timer", oxnas_rps_timer_init);