Received: by 2002:a25:1506:0:0:0:0:0 with SMTP id 6csp4798822ybv; Mon, 17 Feb 2020 06:13:11 -0800 (PST) X-Google-Smtp-Source: APXvYqwDaXogRYwg6+nEUk0YZyGm/URG6EuW8VMCMODhTbSKqdxYLP1EfP/eVhRcbGlA+s6QBq9A X-Received: by 2002:a05:6830:1d5b:: with SMTP id p27mr11697575oth.263.1581948790992; Mon, 17 Feb 2020 06:13:10 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1581948790; cv=none; d=google.com; s=arc-20160816; b=wdVUbF/XZSCnYJg+T3cX9asY8VSc83a2AoMrwpmAZa1rP9Dd7DrFRPZ2E3+ZYNbjOH t88fmsn7WOTIfXvLkGCmj0ETifGk/UcSMbFb3V/EPRUirJO6lLdK1yJLKcjwllEJ57M+ 8rysDWdyY+MqO57HuyFVrbDELuF75abQiOvjypOvh/t7oxaUouim2wsEQ/F2sNXE5Ti6 pSQKSlK9OM5Jf+/W49v7zZwqelir4zOwj5zJolahsxU2EgK+PXHHEdvTgyfAiUsmhMNe dPzEfk33QvPBLR6k42Ho71flKEDCWDPcCFp6D+Kerg+VsdjjjZzllXj7PZeSdol9Uv90 Fkug== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=5KigXvuFpqE23hpgMkeSflEO5elhAPnlDRqpTozyIKE=; b=QLzmm/fH0NVqGlsn58MrDAoqJFL1dxXkxEoOMmtmLqaGIvKnEG9J3L05kWLCrTO9lz 1Uz4dMDSO4c6g2+umu+oE/InPzxGKzCBdbD0wpq5AyQltGV7Py8ZSb0UVCmFwLkfvmLM M02J3ioajZMdYY4+Zlg/6/Te+RuIxZ1YbgC3qwckBkOVH8dwoOerX0+Y3ipmo2IEc8D8 2MriPlpaE8PEoktrdOrayNtqigvEdpHaSoO3/ki81uWMJNg27yAdBdB/qVv7pY87P8fA Y8ROs0+/8xGXroDeqRhIBFYhhHpQosUT5M9al7GVSdVtNgWo2XxCf/wpMD1ziAwc0v4H v4xA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@st.com header.s=STMicroelectronics header.b=0iugv1Pp; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=st.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id m12si6263462oim.195.2020.02.17.06.12.58; Mon, 17 Feb 2020 06:13:10 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@st.com header.s=STMicroelectronics header.b=0iugv1Pp; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=st.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728605AbgBQNqL (ORCPT + 99 others); Mon, 17 Feb 2020 08:46:11 -0500 Received: from mx08-00178001.pphosted.com ([91.207.212.93]:55286 "EHLO mx07-00178001.pphosted.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1727193AbgBQNqK (ORCPT ); Mon, 17 Feb 2020 08:46:10 -0500 Received: from pps.filterd (m0046661.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.16.0.42/8.16.0.42) with SMTP id 01HDhVxR007656; Mon, 17 Feb 2020 14:45:56 +0100 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=st.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-type; s=STMicroelectronics; bh=5KigXvuFpqE23hpgMkeSflEO5elhAPnlDRqpTozyIKE=; b=0iugv1PpC/rgsMmTSHrLqccos3D1QwB/pctcQutz9JVat01Hn9jYsNsVra5tzgXqHMcl NAIvFIZznwe/YmJkqmOt7SaB6w71jLDaKOc77dfISPK3jc1wuhPvF/jNuB3jgC9TTdY5 ZW1rr+sXPHmPrB+SczxBXY41NEoYYivAiE7lIhLp8ZVdj2NDBMrlnsc9j1wXlfS+v7OC +1wNyH4zshGPZXOin4f1O6+PpWL+gypSonrv3vAKnGvjXwYVXrfp7varZgcrCzNg5dWw xIPw+KvMEI+yskBvTcMfnrNjgvnbL3UJqtfrmxe5dUvDNgxPyGruI6/t+J9/pdfuMYVA ww== Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-00178001.pphosted.com with ESMTP id 2y68dp3ay7-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 17 Feb 2020 14:45:56 +0100 Received: from euls16034.sgp.st.com (euls16034.sgp.st.com [10.75.44.20]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 92183100038; Mon, 17 Feb 2020 14:45:51 +0100 (CET) Received: from Webmail-eu.st.com (sfhdag3node3.st.com [10.75.127.9]) by euls16034.sgp.st.com (STMicroelectronics) with ESMTP id 8268E2FF5C7; Mon, 17 Feb 2020 14:45:51 +0100 (CET) Received: from localhost (10.75.127.44) by SFHDAG3NODE3.st.com (10.75.127.9) with Microsoft SMTP Server (TLS) id 15.0.1347.2; Mon, 17 Feb 2020 14:45:50 +0100 From: Benjamin Gaignard To: , , , , , , , CC: , , , , Benjamin Gaignard , Pascal Paillet Subject: [PATCH v4 3/3] clocksource: Add Low Power STM32 timers driver Date: Mon, 17 Feb 2020 14:45:46 +0100 Message-ID: <20200217134546.14562-4-benjamin.gaignard@st.com> X-Mailer: git-send-email 2.15.0 In-Reply-To: <20200217134546.14562-1-benjamin.gaignard@st.com> References: <20200217134546.14562-1-benjamin.gaignard@st.com> MIME-Version: 1.0 Content-Type: text/plain X-Originating-IP: [10.75.127.44] X-ClientProxiedBy: SFHDAG7NODE1.st.com (10.75.127.19) To SFHDAG3NODE3.st.com (10.75.127.9) X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.138,18.0.572 definitions=2020-02-17_08:2020-02-17,2020-02-17 signatures=0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Benjamin Gaignard Implement clock event driver using low power STM32 timers. Low power timer counters running even when CPUs are stopped. It could be used as clock event broadcaster to wake up CPUs but not like a clocksource because each it rise an interrupt the counter restart from 0. Low power timers have a 16 bits counter and a prescaler which allow to divide the clock per power of 2 to up 128 to target a 32KHz rate. Signed-off-by: Benjamin Gaignard Signed-off-by: Pascal Paillet --- version 4: - move defines in mfd/stm32-lptimer.h - change compatiblename - reword commit message - make driver Kconfig depends of MFD_STM32_LPTIMER - remove useless include - remove rate and clk fields from the private structure - to add comments about the registers sequence in stm32_clkevent_lp_set_timer - rework probe function and use devm_request_irq() - do not allow module to be removed - make sure that wakeup interrupt is set drivers/clocksource/Kconfig | 7 ++ drivers/clocksource/Makefile | 1 + drivers/clocksource/timer-stm32-lp.c | 213 +++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 drivers/clocksource/timer-stm32-lp.c diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index cc909e465823..9fc2b513db6f 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -292,6 +292,13 @@ config CLKSRC_STM32 select CLKSRC_MMIO select TIMER_OF +config CLKSRC_STM32_LP + bool "Low power clocksource for STM32 SoCs" + depends on MFD_STM32_LPTIMER || COMPILE_TEST + help + This option enables support for STM32 low power clockevent available + on STM32 SoCs + config CLKSRC_MPS2 bool "Clocksource for MPS2 SoCs" if COMPILE_TEST depends on GENERIC_SCHED_CLOCK diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 713686faa549..c00fffbd4769 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_BCM_KONA_TIMER) += bcm_kona_timer.o obj-$(CONFIG_CADENCE_TTC_TIMER) += timer-cadence-ttc.o obj-$(CONFIG_CLKSRC_EFM32) += timer-efm32.o obj-$(CONFIG_CLKSRC_STM32) += timer-stm32.o +obj-$(CONFIG_CLKSRC_STM32_LP) += timer-stm32-lp.o obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o obj-$(CONFIG_CLKSRC_LPC32XX) += timer-lpc32xx.o obj-$(CONFIG_CLKSRC_MPS2) += mps2-timer.o diff --git a/drivers/clocksource/timer-stm32-lp.c b/drivers/clocksource/timer-stm32-lp.c new file mode 100644 index 000000000000..50eecdb88216 --- /dev/null +++ b/drivers/clocksource/timer-stm32-lp.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics 2019 - All Rights Reserved + * Authors: Benjamin Gaignard for STMicroelectronics. + * Pascal Paillet for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CFGR_PSC_OFFSET 9 +#define STM32_LP_RATING 400 +#define STM32_TARGET_CLKRATE (32000 * HZ) +#define STM32_LP_MAX_PSC 7 + +struct stm32_lp_private { + struct regmap *reg; + struct clock_event_device clkevt; + unsigned long period; +}; + +static struct stm32_lp_private* +to_priv(struct clock_event_device *clkevt) +{ + return container_of(clkevt, struct stm32_lp_private, clkevt); +} + +static int stm32_clkevent_lp_shutdown(struct clock_event_device *clkevt) +{ + struct stm32_lp_private *priv = to_priv(clkevt); + + regmap_write(priv->reg, STM32_LPTIM_CR, 0); + regmap_write(priv->reg, STM32_LPTIM_IER, 0); + /* clear pending flags */ + regmap_write(priv->reg, STM32_LPTIM_ICR, STM32_LPTIM_ARRMCF); + + return 0; +} + +static int stm32_clkevent_lp_set_timer(unsigned long evt, + struct clock_event_device *clkevt, + int is_periodic) +{ + struct stm32_lp_private *priv = to_priv(clkevt); + + /* disable LPTIMER to be able to write into IER register*/ + regmap_write(priv->reg, STM32_LPTIM_CR, 0); + /* enable ARR interrupt */ + regmap_write(priv->reg, STM32_LPTIM_IER, STM32_LPTIM_ARRMIE); + /* enable LPTIMER to be able to write into ARR register */ + regmap_write(priv->reg, STM32_LPTIM_CR, STM32_LPTIM_ENABLE); + /* set next event counter */ + regmap_write(priv->reg, STM32_LPTIM_ARR, evt); + + /* start counter */ + if (is_periodic) + regmap_write(priv->reg, STM32_LPTIM_CR, + STM32_LPTIM_CNTSTRT | STM32_LPTIM_ENABLE); + else + regmap_write(priv->reg, STM32_LPTIM_CR, + STM32_LPTIM_SNGSTRT | STM32_LPTIM_ENABLE); + + return 0; +} + +static int stm32_clkevent_lp_set_next_event(unsigned long evt, + struct clock_event_device *clkevt) +{ + return stm32_clkevent_lp_set_timer(evt, clkevt, + clockevent_state_periodic(clkevt)); +} + +static int stm32_clkevent_lp_set_periodic(struct clock_event_device *clkevt) +{ + struct stm32_lp_private *priv = to_priv(clkevt); + + return stm32_clkevent_lp_set_timer(priv->period, clkevt, true); +} + +static int stm32_clkevent_lp_set_oneshot(struct clock_event_device *clkevt) +{ + struct stm32_lp_private *priv = to_priv(clkevt); + + return stm32_clkevent_lp_set_timer(priv->period, clkevt, false); +} + +static irqreturn_t stm32_clkevent_lp_irq_handler(int irq, void *dev_id) +{ + struct clock_event_device *clkevt = (struct clock_event_device *)dev_id; + struct stm32_lp_private *priv = to_priv(clkevt); + + regmap_write(priv->reg, STM32_LPTIM_ICR, STM32_LPTIM_ARRMCF); + + clkevt->event_handler(clkevt); + + return IRQ_HANDLED; +} + +static void stm32_clkevent_lp_set_prescaler(struct stm32_lp_private *priv, + unsigned long *rate) +{ + int i; + + for (i = 0; i <= STM32_LP_MAX_PSC; i++) { + if (DIV_ROUND_CLOSEST(*rate, 1 << i) < STM32_TARGET_CLKRATE) + break; + } + + regmap_write(priv->reg, STM32_LPTIM_CFGR, i << CFGR_PSC_OFFSET); + + /* Adjust rate and period given the prescaler value */ + *rate = DIV_ROUND_CLOSEST(*rate, (1 << i)); + priv->period = DIV_ROUND_UP(*rate, HZ); +} + +static void stm32_clkevent_lp_init(struct stm32_lp_private *priv, + struct device_node *np, unsigned long rate) +{ + priv->clkevt.name = np->full_name; + priv->clkevt.cpumask = cpu_possible_mask; + priv->clkevt.features = CLOCK_EVT_FEAT_PERIODIC | + CLOCK_EVT_FEAT_ONESHOT; + priv->clkevt.set_state_shutdown = stm32_clkevent_lp_shutdown; + priv->clkevt.set_state_periodic = stm32_clkevent_lp_set_periodic; + priv->clkevt.set_state_oneshot = stm32_clkevent_lp_set_oneshot; + priv->clkevt.set_next_event = stm32_clkevent_lp_set_next_event; + priv->clkevt.rating = STM32_LP_RATING; + + clockevents_config_and_register(&priv->clkevt, rate, 0x1, + STM32_LPTIM_MAX_ARR); +} + +static int stm32_clkevent_lp_probe(struct platform_device *pdev) +{ + struct stm32_lptimer *ddata = dev_get_drvdata(pdev->dev.parent); + struct stm32_lp_private *priv; + unsigned long rate; + int ret, irq; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->reg = ddata->regmap; + ret = clk_prepare_enable(ddata->clk); + if (ret) + return -EINVAL; + + rate = clk_get_rate(ddata->clk); + if (!rate) + goto out_clk_disable; + + irq = irq_of_parse_and_map(pdev->dev.parent->of_node, 0); + if (!irq) + goto out_clk_disable; + + if (of_property_read_bool(pdev->dev.parent->of_node, "wakeup-source")) { + ret = device_init_wakeup(&pdev->dev, true); + if (ret) + goto out_clk_disable; + + ret = dev_pm_set_wake_irq(&pdev->dev, irq); + if (ret) + goto out_clk_disable; + } + + ret = devm_request_irq(&pdev->dev, irq, stm32_clkevent_lp_irq_handler, + IRQF_TIMER, pdev->name, &priv->clkevt); + if (ret) + goto out_clk_disable; + + stm32_clkevent_lp_set_prescaler(priv, &rate); + + stm32_clkevent_lp_init(priv, pdev->dev.parent->of_node, rate); + + return 0; + +out_clk_disable: + clk_disable_unprepare(ddata->clk); + return -EINVAL; +} + +static int stm32_clkevent_lp_remove(struct platform_device *pdev) +{ + return -EBUSY; /* cannot unregister clockevent */ +} + +static const struct of_device_id stm32_clkevent_lp_of_match[] = { + { .compatible = "st,stm32-lptimer-timer", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_clkevent_lp_of_match); + +static struct platform_driver stm32_clkevent_lp_driver = { + .probe = stm32_clkevent_lp_probe, + .remove = stm32_clkevent_lp_remove, + .driver = { + .name = "stm32-lptimer-timer", + .of_match_table = of_match_ptr(stm32_clkevent_lp_of_match), + }, +}; +module_platform_driver(stm32_clkevent_lp_driver); + +MODULE_ALIAS("platform:stm32-lptimer-timer"); +MODULE_DESCRIPTION("STMicroelectronics STM32 clockevent low power driver"); +MODULE_LICENSE("GPL v2"); -- 2.15.0