From patchwork Tue May 30 21:51:27 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexandre Belloni X-Patchwork-Id: 9755501 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 A0410602F0 for ; Tue, 30 May 2017 22:04:11 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9113426E51 for ; Tue, 30 May 2017 22:04:11 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 84F41283BA; Tue, 30 May 2017 22:04:11 +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=-1.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id DAE1A26E51 for ; Tue, 30 May 2017 22:04:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=+zPmjO+2R3ya+FWyWyTsTl06Cr4AQzgDNOV4l55eqy0=; b=OUay3hyBtPmgMvhEqaGrEPeVHS 7iwAxMHnaAzM4WZLPqBdfjbGY3MqgK3mCfh90QFB4CQM1glVi46xi8wZTyRSXhHFD2sZ+I/0zoEsr Z4zyzJ0lMmEoMvgYt7+lt/xpCKg3TzQJvoI6zhVwnnvET7eE8aQQmPAFPVNNpnM0ZsjFLVoSsLq4+ Hd3UBVzK43sYv+r1Rcfu5F8TRPhwmcjpivl4gtazZfPhc/B4T4UOt+FbHfFLSvzqcl4w4U+IaPS5K D/R0qcQQ5E5s8I6PhZYnjbbP0wVQO2ZqoFQa4r+roZNem6TWR3fExNwqhJ0ZeHmzwakEOuFTTAXdq wtvEoIKg==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1dFpFJ-0001qt-6I; Tue, 30 May 2017 22:04:09 +0000 Received: from mail.free-electrons.com ([62.4.15.54]) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1dFp4L-0005OR-V9 for linux-arm-kernel@lists.infradead.org; Tue, 30 May 2017 21:52:58 +0000 Received: by mail.free-electrons.com (Postfix, from userid 110) id 424DB20F33; Tue, 30 May 2017 23:52:37 +0200 (CEST) Received: from localhost (unknown [88.191.26.124]) by mail.free-electrons.com (Postfix) with ESMTPSA id DE47E20F35; Tue, 30 May 2017 23:52:26 +0200 (CEST) From: Alexandre Belloni To: Nicolas Ferre Subject: [PATCH 46/58] clocksource/drivers: Add a new driver for the Atmel ARM TC blocks Date: Tue, 30 May 2017 23:51:27 +0200 Message-Id: <20170530215139.9983-47-alexandre.belloni@free-electrons.com> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20170530215139.9983-1-alexandre.belloni@free-electrons.com> References: <20170530215139.9983-1-alexandre.belloni@free-electrons.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170530_145250_288842_49A28907 X-CRM114-Status: GOOD ( 23.97 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Boris Brezillon , Daniel Lezcano , linux-kernel@vger.kernel.org, Alexandre Belloni , Thomas Gleixner , linux-arm-kernel@lists.infradead.org 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 a driver for the Atmel Timer Counter Blocks. This driver provides a clocksource and a clockevent device. The clockevent device is linked to the clocksource counter and so it will run at the same frequency. This driver uses regmap and syscon to be able to probe early in the boot and avoid having to switch on the TCB clocksource later. Using regmap also means that unused TCB channels may be used by other drivers (PWM for example). Cc: Daniel Lezcano Cc: Thomas Gleixner Signed-off-by: Alexandre Belloni --- drivers/clocksource/Kconfig | 13 ++ drivers/clocksource/Makefile | 3 +- drivers/clocksource/timer-atmel-tcbclksrc.c | 252 ++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 drivers/clocksource/timer-atmel-tcbclksrc.c diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 545d541ae20e..cbd710db3c09 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -418,6 +418,19 @@ config ATMEL_ST help Support for the Atmel ST timer. +config ATMEL_ARM_TCB_CLKSRC + bool "TC Block Clocksource" + select REGMAP_MMIO + depends on GENERIC_CLOCKEVENTS + depends on SOC_AT91RM9200 || SOC_AT91SAM9 || SOC_SAMA5 || COMPILE_TEST + default SOC_AT91RM9200 || SOC_AT91SAM9 || SOC_SAMA5 + help + Select this to get a high precision clocksource based on a + TC block with a 5+ MHz base clock rate. + On platforms with 16-bit counters, two timer channels are combined + to make a single 32-bit timer. + It can also be used as a clock event device supporting oneshot mode. + config CLKSRC_METAG_GENERIC def_bool y if METAG help diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 2b5b56a6f00f..53a0b40e0db2 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -2,7 +2,8 @@ obj-$(CONFIG_CLKSRC_PROBE) += clksrc-probe.o obj-$(CONFIG_CLKEVT_PROBE) += clkevt-probe.o obj-$(CONFIG_ATMEL_PIT) += timer-atmel-pit.o obj-$(CONFIG_ATMEL_ST) += timer-atmel-st.o -obj-$(CONFIG_ATMEL_TCB_CLKSRC) += tcb_clksrc.o +obj-$(CONFIG_ATMEL_TCB_CLKSRC) += tcb_clksrc.o +obj-$(CONFIG_ATMEL_ARM_TCB_CLKSRC) += timer-atmel-tcbclksrc.o obj-$(CONFIG_X86_PM_TIMER) += acpi_pm.o obj-$(CONFIG_SCx200HR_TIMER) += scx200_hrt.o obj-$(CONFIG_CS5535_CLOCK_EVENT_SRC) += cs5535-clockevt.o diff --git a/drivers/clocksource/timer-atmel-tcbclksrc.c b/drivers/clocksource/timer-atmel-tcbclksrc.c new file mode 100644 index 000000000000..f18d177bfdea --- /dev/null +++ b/drivers/clocksource/timer-atmel-tcbclksrc.c @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct atmel_tcb_clksrc { + char name[20]; + struct clocksource clksrc; + struct regmap *regmap; + struct clk *clk[2]; + int channels[2]; + int bits; + int irq; + bool registered; +} tc = { + .clksrc = { + .rating = 200, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + }, +}; + +static u64 tc_get_cycles(struct clocksource *cs) +{ + u32 lower, upper, tmp; + + do { + regmap_read(tc.regmap, ATMEL_TC_CV(1), &upper); + regmap_read(tc.regmap, ATMEL_TC_CV(0), &lower); + regmap_read(tc.regmap, ATMEL_TC_CV(1), &tmp); + } while (upper != tmp); + + return (upper << 16) | lower; +} + +static u64 tc_get_cycles32(struct clocksource *cs) +{ + u32 val; + + regmap_read(tc.regmap, ATMEL_TC_CV(tc.channels[0]), &val); + + return val; +} + +static u64 notrace tc_sched_clock_read(void) +{ + return tc_get_cycles(&tc.clksrc); +} + +static u64 notrace tc_sched_clock_read32(void) +{ + return tc_get_cycles32(&tc.clksrc); +} + +static void __init tcb_setup_dual_chan(struct atmel_tcb_clksrc *tc, + int mck_divisor_idx) +{ + /* first channel: waveform mode, input mclk/8, clock TIOA on overflow */ + regmap_write(tc->regmap, ATMEL_TC_CMR(tc->channels[0]), + mck_divisor_idx /* likely divide-by-8 */ + | ATMEL_TC_CMR_WAVE + | ATMEL_TC_CMR_WAVESEL_UP /* free-run */ + | ATMEL_TC_CMR_ACPA(SET) /* TIOA rises at 0 */ + | ATMEL_TC_CMR_ACPC(CLEAR)); /* (duty cycle 50%) */ + regmap_write(tc->regmap, ATMEL_TC_RA(tc->channels[0]), 0x0000); + regmap_write(tc->regmap, ATMEL_TC_RC(tc->channels[0]), 0x8000); + regmap_write(tc->regmap, ATMEL_TC_IDR(tc->channels[0]), 0xff); /* no irqs */ + regmap_write(tc->regmap, ATMEL_TC_CCR(tc->channels[0]), + ATMEL_TC_CCR_CLKEN); + + /* second channel: waveform mode, input TIOA */ + regmap_write(tc->regmap, ATMEL_TC_CMR(tc->channels[1]), + ATMEL_TC_CMR_XC(tc->channels[1]) /* input: TIOA */ + | ATMEL_TC_CMR_WAVE + | ATMEL_TC_CMR_WAVESEL_UP); /* free-run */ + regmap_write(tc->regmap, ATMEL_TC_IDR(tc->channels[1]), 0xff); /* no irqs */ + regmap_write(tc->regmap, ATMEL_TC_CCR(tc->channels[1]), + ATMEL_TC_CCR_CLKEN); + + /* chain both channel, we assume the previous channel */ + regmap_write(tc->regmap, ATMEL_TC_BMR, + ATMEL_TC_BMR_TCXC(1 + tc->channels[1], tc->channels[1])); + /* then reset all the timers */ + regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC); +} + +static void __init tcb_setup_single_chan(struct atmel_tcb_clksrc *tc, + int mck_divisor_idx) +{ + /* channel 0: waveform mode, input mclk/8 */ + regmap_write(tc->regmap, ATMEL_TC_CMR(tc->channels[0]), + mck_divisor_idx /* likely divide-by-8 */ + | ATMEL_TC_CMR_WAVE + | ATMEL_TC_CMR_WAVESEL_UP /* free-run */ + ); + regmap_write(tc->regmap, ATMEL_TC_IDR(tc->channels[0]), 0xff); /* no irqs */ + regmap_write(tc->regmap, ATMEL_TC_CCR(tc->channels[0]), + ATMEL_TC_CCR_CLKEN); + + /* then reset all the timers */ + regmap_write(tc->regmap, ATMEL_TC_BCR, ATMEL_TC_BCR_SYNC); +} + +static int __init tcb_clksrc_register(struct device_node *node, + struct regmap *regmap, int channel, + int channel1, int irq, int bits) +{ + u32 rate, divided_rate = 0; + int best_divisor_idx = -1; + int i, err = -1; + u64 (*tc_sched_clock)(void); + + tc.regmap = regmap; + tc.channels[0] = channel; + tc.channels[1] = channel1; + tc.irq = irq; + tc.bits = bits; + + tc.clk[0] = tcb_clk_get(node, tc.channels[0]); + if (IS_ERR(tc.clk[0])) + return PTR_ERR(tc.clk[0]); + err = clk_prepare_enable(tc.clk[0]); + if (err) { + pr_debug("can't enable T0 clk\n"); + goto err_clk; + } + + /* How fast will we be counting? Pick something over 5 MHz. */ + rate = (u32)clk_get_rate(tc.clk[0]); + for (i = 0; i < 5; i++) { + unsigned int divisor = atmel_tc_divisors[i]; + unsigned int tmp; + + if (!divisor) + continue; + + tmp = rate / divisor; + pr_debug("TC: %u / %-3u [%d] --> %u\n", rate, divisor, i, tmp); + if (best_divisor_idx > 0) { + if (tmp < 5 * 1000 * 1000) + continue; + } + divided_rate = tmp; + best_divisor_idx = i; + } + + if (tc.bits == 32) { + tc.clksrc.read = tc_get_cycles32; + tcb_setup_single_chan(&tc, best_divisor_idx); + tc_sched_clock = tc_sched_clock_read32; + snprintf(tc.name, sizeof(tc.name), "%s:%d", + kbasename(node->parent->full_name), tc.channels[0]); + } else { + tc.clk[1] = tcb_clk_get(node, tc.channels[1]); + if (IS_ERR(tc.clk[1])) + goto err_disable_t0; + + err = clk_prepare_enable(tc.clk[1]); + if (err) { + pr_debug("can't enable T1 clk\n"); + goto err_clk1; + } + tc.clksrc.read = tc_get_cycles, + tcb_setup_dual_chan(&tc, best_divisor_idx); + tc_sched_clock = tc_sched_clock_read; + snprintf(tc.name, sizeof(tc.name), "%s:%d,%d", + kbasename(node->parent->full_name), tc.channels[0], + tc.channels[1]); + } + + pr_debug("%s at %d.%03d MHz\n", tc.name, + divided_rate / 1000000, + ((divided_rate + 500000) % 1000000) / 1000); + + tc.clksrc.name = tc.name; + + err = clocksource_register_hz(&tc.clksrc, divided_rate); + if (err) + goto err_disable_t1; + + sched_clock_register(tc_sched_clock, 32, divided_rate); + + tc.registered = true; + + return 0; + +err_disable_t1: + if (tc.bits == 16) + clk_disable_unprepare(tc.clk[1]); + +err_clk1: + if (tc.bits == 16) + clk_put(tc.clk[1]); + +err_disable_t0: + clk_disable_unprepare(tc.clk[0]); + +err_clk: + clk_put(tc.clk[0]); + + pr_err("%s: unable to register clocksource/clockevent\n", + tc.clksrc.name); + + return err; +} + +static int __init tcb_clksrc_init(struct device_node *node) +{ + const struct of_device_id *match; + const struct atmel_tcb_info *tcb_info; + struct regmap *regmap; + u32 channel; + int bits, irq, err, chan1 = -1; + + if (tc.registered) + return -ENODEV; + + regmap = syscon_node_to_regmap(node->parent); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + match = of_match_node(atmel_tcb_dt_ids, node->parent); + tcb_info = match->data; + bits = tcb_info->bits; + + err = of_property_read_u32_index(node, "reg", 0, &channel); + if (err) + return err; + + irq = tcb_irq_get(node, channel); + if (irq < 0) + return irq; + + if (bits == 16) { + of_property_read_u32_index(node, "reg", 1, &chan1); + if (chan1 == -1) { + pr_err("%s: clocksource needs two channels\n", + node->parent->full_name); + return -EINVAL; + } + } + + return tcb_clksrc_register(node, regmap, channel, chan1, irq, bits); +} +CLOCKSOURCE_OF_DECLARE(atmel_tcb_clksrc, "atmel,tcb-timer", + tcb_clksrc_init);