From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.1 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI, SIGNED_OFF_BY,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 39351C04AAC for ; Mon, 20 May 2019 15:28:16 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id E3FF02081C for ; Mon, 20 May 2019 15:28:15 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=st.com header.i=@st.com header.b="aO8HpXI7" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2392164AbfETP2O (ORCPT ); Mon, 20 May 2019 11:28:14 -0400 Received: from mx07-00178001.pphosted.com ([62.209.51.94]:55284 "EHLO mx07-00178001.pphosted.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388226AbfETP1y (ORCPT ); Mon, 20 May 2019 11:27:54 -0400 Received: from pps.filterd (m0046668.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.16.0.27/8.16.0.27) with SMTP id x4KFLUFE002106; Mon, 20 May 2019 17:27:19 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=st.com; h=from : to : cc : subject : date : message-id : references : in-reply-to : content-type : content-transfer-encoding : mime-version; s=STMicroelectronics; bh=Z9x1RsoFBv1VevDzyNrpT9CEsTNBt+qZC3x8CkrHJ1g=; b=aO8HpXI7QMutUlzBjnezfp0AAE44P8S1B2o+x1R4osrQgRTvjMVM/jn7uaxOH2Pka/TF 4VR/tI5QZ8dxEuxhzPJrJle75+/mgcZEHXC7wbvTQpCETJ0X978ff9Ir6FsaoJO6lyWC xp2EF9NmBa5bi4Rie/PNYUfPKOCtyz1/56EqcqGl1DBWBrPG9TwG36VTPNwU4cCtbA2q u2fZiov4t15sYRUsx3LjBXqjBI7A0vOo1JBr8Y/2JJjA2uwZuIZIYqeUSkoOAAbuAieh /OOZfObOeXBuqqAk6ITCKYNWyao7wnR3ebx0e0dWUxSTAg2S65hnKE0BTIVWV0PUHLdf Dw== Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-00178001.pphosted.com with ESMTP id 2sj7h0mb1x-1 (version=TLSv1 cipher=ECDHE-RSA-AES256-SHA bits=256 verify=NOT); Mon, 20 May 2019 17:27:19 +0200 Received: from zeta.dmz-eu.st.com (zeta.dmz-eu.st.com [164.129.230.9]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 30CA73D; Mon, 20 May 2019 15:27:18 +0000 (GMT) Received: from Webmail-eu.st.com (sfhdag3node3.st.com [10.75.127.9]) by zeta.dmz-eu.st.com (STMicroelectronics) with ESMTP id F082C2BAC; Mon, 20 May 2019 15:27:17 +0000 (GMT) Received: from SFHDAG5NODE1.st.com (10.75.127.13) by SFHDAG3NODE3.st.com (10.75.127.9) with Microsoft SMTP Server (TLS) id 15.0.1347.2; Mon, 20 May 2019 17:27:17 +0200 Received: from SFHDAG5NODE1.st.com ([fe80::cc53:528c:36c8:95f6]) by SFHDAG5NODE1.st.com ([fe80::cc53:528c:36c8:95f6%20]) with mapi id 15.00.1347.000; Mon, 20 May 2019 17:27:17 +0200 From: Gerald BAEZA To: "will.deacon@arm.com" , "mark.rutland@arm.com" , "robh+dt@kernel.org" , "mcoquelin.stm32@gmail.com" , Alexandre TORGUE , "corbet@lwn.net" , "linux@armlinux.org.uk" , "olof@lixom.net" , "horms+renesas@verge.net.au" , "arnd@arndb.de" CC: "linux-arm-kernel@lists.infradead.org" , "devicetree@vger.kernel.org" , "linux-stm32@st-md-mailman.stormreply.com" , "linux-kernel@vger.kernel.org" , "linux-doc@vger.kernel.org" , Gerald BAEZA Subject: [PATCH v2 3/5] perf: stm32: ddrperfm driver creation Thread-Topic: [PATCH v2 3/5] perf: stm32: ddrperfm driver creation Thread-Index: AQHVDyCCI5Cs89crU0SFN7k5vdHanA== Date: Mon, 20 May 2019 15:27:17 +0000 Message-ID: <1558366019-24214-4-git-send-email-gerald.baeza@st.com> References: <1558366019-24214-1-git-send-email-gerald.baeza@st.com> In-Reply-To: <1558366019-24214-1-git-send-email-gerald.baeza@st.com> Accept-Language: fr-FR, en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-ms-exchange-messagesentrepresentingtype: 1 x-ms-exchange-transport-fromentityheader: Hosted x-originating-ip: [10.75.127.45] Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:,, definitions=2019-05-20_07:,, signatures=0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org The DDRPERFM is the DDR Performance Monitor embedded in STM32MP1 SOC. This perf drivers supports the read, write, activate, idle and total time counters, described in the reference manual RM0436. A 'bandwidth' attribute is added in the 'ddrperfm' event_source in order to directly get the read and write bandwidths (in MB/s), from the last read, write and total time counters reading. This attribute is aside the 'events' attributes group because it is not a counter, as seen by perf tool. Signed-off-by: Gerald Baeza --- drivers/perf/Kconfig | 6 + drivers/perf/Makefile | 1 + drivers/perf/stm32_ddr_pmu.c | 512 +++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 519 insertions(+) create mode 100644 drivers/perf/stm32_ddr_pmu.c diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig index a94e586..9add8a7 100644 --- a/drivers/perf/Kconfig +++ b/drivers/perf/Kconfig @@ -105,6 +105,12 @@ config THUNDERX2_PMU The SoC has PMU support in its L3 cache controller (L3C) and in the DDR4 Memory Controller (DMC). =20 +config STM32_DDR_PMU + tristate "STM32 DDR PMU" + depends on MACH_STM32MP157 + help + Support for STM32 DDR performance monitor (DDRPERFM). + config XGENE_PMU depends on ARCH_XGENE bool "APM X-Gene SoC PMU" diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile index 3048994..fa64719 100644 --- a/drivers/perf/Makefile +++ b/drivers/perf/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_ARM_SMMU_V3_PMU) +=3D arm_smmuv3_pmu.o obj-$(CONFIG_HISI_PMU) +=3D hisilicon/ obj-$(CONFIG_QCOM_L2_PMU) +=3D qcom_l2_pmu.o obj-$(CONFIG_QCOM_L3_PMU) +=3D qcom_l3_pmu.o +obj-$(CONFIG_STM32_DDR_PMU) +=3D stm32_ddr_pmu.o obj-$(CONFIG_THUNDERX2_PMU) +=3D thunderx2_pmu.o obj-$(CONFIG_XGENE_PMU) +=3D xgene_pmu.o obj-$(CONFIG_ARM_SPE_PMU) +=3D arm_spe_pmu.o diff --git a/drivers/perf/stm32_ddr_pmu.c b/drivers/perf/stm32_ddr_pmu.c new file mode 100644 index 0000000..ae4a813 --- /dev/null +++ b/drivers/perf/stm32_ddr_pmu.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is the STM32 DDR performance monitor (DDRPERFM) driver + * + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + * Author: Gerald Baeza + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define POLL_MS 4000 /* The counter roll over after ~8s @533MHz */ +#define WORD_LENGTH 4 /* Bytes */ +#define BURST_LENGTH 8 /* Words */ + +#define DDRPERFM_CTL 0x000 +#define DDRPERFM_CFG 0x004 +#define DDRPERFM_STATUS 0x008 +#define DDRPERFM_CCR 0x00C +#define DDRPERFM_IER 0x010 +#define DDRPERFM_ISR 0x014 +#define DDRPERFM_ICR 0x018 +#define DDRPERFM_TCNT 0x020 +#define DDRPERFM_CNT(X) (0x030 + 8 * (X)) +#define DDRPERFM_HWCFG 0x3F0 +#define DDRPERFM_VER 0x3F4 +#define DDRPERFM_ID 0x3F8 +#define DDRPERFM_SID 0x3FC + +#define CTL_START 0x00000001 +#define CTL_STOP 0x00000002 +#define CCR_CLEAR_ALL 0x8000000F +#define SID_MAGIC_ID 0xA3C5DD01 + +#define STRING "Read =3D %llu, Write =3D %llu, Read & Write =3D %llu (MB/s= )\n" + +enum { + READ_CNT, + WRITE_CNT, + ACTIVATE_CNT, + IDLE_CNT, + TIME_CNT, + PMU_NR_COUNTERS +}; + +struct stm32_ddr_pmu { + struct pmu pmu; + void __iomem *membase; + struct clk *clk; + struct clk *clk_ddr; + unsigned long clk_ddr_rate; + struct hrtimer hrtimer; + ktime_t poll_period; + spinlock_t lock; /* for shared registers access */ + struct perf_event *events[PMU_NR_COUNTERS]; + u64 events_cnt[PMU_NR_COUNTERS]; +}; + +static inline struct stm32_ddr_pmu *pmu_to_stm32_ddr_pmu(struct pmu *p) +{ + return container_of(p, struct stm32_ddr_pmu, pmu); +} + +static inline struct stm32_ddr_pmu *hrtimer_to_stm32_ddr_pmu(struct hrtime= r *h) +{ + return container_of(h, struct stm32_ddr_pmu, hrtimer); +} + +static u64 stm32_ddr_pmu_compute_bw(struct stm32_ddr_pmu *stm32_ddr_pmu, + int counter) +{ + u64 bw =3D stm32_ddr_pmu->events_cnt[counter], tmp; + u64 div =3D stm32_ddr_pmu->events_cnt[TIME_CNT]; + u32 prediv =3D 1, premul =3D 1; + + if (bw && div) { + /* Maximize the dividend into 64 bits */ + while ((bw < 0x8000000000000000ULL) && + (premul < 0x40000000UL)) { + bw =3D bw << 1; + premul *=3D 2; + } + /* Minimize the dividor to fit in 32 bits */ + while ((div > 0xffffffffUL) && (prediv < 0x40000000UL)) { + div =3D div >> 1; + prediv *=3D 2; + } + /* Divide counter per time and multiply per DDR settings */ + do_div(bw, div); + tmp =3D bw * BURST_LENGTH * WORD_LENGTH; + tmp *=3D stm32_ddr_pmu->clk_ddr_rate; + if (tmp < bw) + goto error; + bw =3D tmp; + /* Cancel the prediv and premul factors */ + while (prediv > 1) { + bw =3D bw >> 1; + prediv /=3D 2; + } + while (premul > 1) { + bw =3D bw >> 1; + premul /=3D 2; + } + /* Convert MHz to Hz and B to MB, to finally get MB/s */ + tmp =3D bw * 1000000; + if (tmp < bw) + goto error; + bw =3D tmp; + premul =3D 1024 * 1024; + while (premul > 1) { + bw =3D bw >> 1; + premul /=3D 2; + } + } + return bw; + +error: + pr_warn("stm32-ddr-pmu: overflow detected\n"); + return 0; +} + +static void stm32_ddr_pmu_event_configure(struct perf_event *event) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long lock_flags, config_base =3D event->hw.config_base; + u32 val; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + if (config_base < TIME_CNT) { + val =3D readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_CFG); + val |=3D (1 << config_base); + writel_relaxed(val, stm32_ddr_pmu->membase + DDRPERFM_CFG); + } + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); +} + +static void stm32_ddr_pmu_event_read(struct perf_event *event) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long flags, config_base =3D event->hw.config_base; + struct hw_perf_event *hw =3D &event->hw; + u64 prev_count, new_count, mask; + u32 val, offset, bit; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, flags); + + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + if (config_base =3D=3D TIME_CNT) { + offset =3D DDRPERFM_TCNT; + bit =3D 1 << 31; + } else { + offset =3D DDRPERFM_CNT(config_base); + bit =3D 1 << config_base; + } + val =3D readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_STATUS); + if (val & bit) + pr_warn("stm32_ddr_pmu hardware overflow\n"); + val =3D readl_relaxed(stm32_ddr_pmu->membase + offset); + writel_relaxed(bit, stm32_ddr_pmu->membase + DDRPERFM_CCR); + writel_relaxed(CTL_START, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + do { + prev_count =3D local64_read(&hw->prev_count); + new_count =3D prev_count + val; + } while (local64_xchg(&hw->prev_count, new_count) !=3D prev_count); + + mask =3D GENMASK_ULL(31, 0); + local64_add(val & mask, &event->count); + + if (new_count < prev_count) + pr_warn("STM32 DDR PMU counter saturated\n"); + + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, flags); +} + +static void stm32_ddr_pmu_event_start(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + struct hw_perf_event *hw =3D &event->hw; + unsigned long lock_flags; + + if (WARN_ON_ONCE(!(hw->state & PERF_HES_STOPPED))) + return; + + if (flags & PERF_EF_RELOAD) + WARN_ON_ONCE(!(hw->state & PERF_HES_UPTODATE)); + + stm32_ddr_pmu_event_configure(event); + + /* Clear all counters to synchronize them, then start */ + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CCR_CLEAR_ALL, stm32_ddr_pmu->membase + DDRPERFM_CCR); + writel_relaxed(CTL_START, stm32_ddr_pmu->membase + DDRPERFM_CTL); + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); + + hw->state =3D 0; +} + +static void stm32_ddr_pmu_event_stop(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long lock_flags, config_base =3D event->hw.config_base; + struct hw_perf_event *hw =3D &event->hw; + u32 val, bit; + + if (WARN_ON_ONCE(hw->state & PERF_HES_STOPPED)) + return; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + if (config_base =3D=3D TIME_CNT) + bit =3D 1 << 31; + else + bit =3D 1 << config_base; + writel_relaxed(bit, stm32_ddr_pmu->membase + DDRPERFM_CCR); + if (config_base < TIME_CNT) { + val =3D readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_CFG); + val &=3D ~bit; + writel_relaxed(val, stm32_ddr_pmu->membase + DDRPERFM_CFG); + } + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); + + hw->state |=3D PERF_HES_STOPPED; + + if (flags & PERF_EF_UPDATE) { + stm32_ddr_pmu_event_read(event); + hw->state |=3D PERF_HES_UPTODATE; + } +} + +static int stm32_ddr_pmu_event_add(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long config_base =3D event->hw.config_base; + struct hw_perf_event *hw =3D &event->hw; + + stm32_ddr_pmu->events_cnt[config_base] =3D 0; + stm32_ddr_pmu->events[config_base] =3D event; + + clk_enable(stm32_ddr_pmu->clk); + hrtimer_start(&stm32_ddr_pmu->hrtimer, stm32_ddr_pmu->poll_period, + HRTIMER_MODE_REL); + + stm32_ddr_pmu_event_configure(event); + + hw->state =3D PERF_HES_STOPPED | PERF_HES_UPTODATE; + + if (flags & PERF_EF_START) + stm32_ddr_pmu_event_start(event, 0); + + return 0; +} + +static void stm32_ddr_pmu_event_del(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long config_base =3D event->hw.config_base; + bool stop =3D true; + int i; + + stm32_ddr_pmu_event_stop(event, PERF_EF_UPDATE); + + stm32_ddr_pmu->events_cnt[config_base] +=3D local64_read(&event->count); + stm32_ddr_pmu->events[config_base] =3D NULL; + + for (i =3D 0; i < PMU_NR_COUNTERS; i++) + if (stm32_ddr_pmu->events[i]) + stop =3D false; + if (stop) + hrtimer_cancel(&stm32_ddr_pmu->hrtimer); + + clk_disable(stm32_ddr_pmu->clk); +} + +static int stm32_ddr_pmu_event_init(struct perf_event *event) +{ + struct hw_perf_event *hw =3D &event->hw; + + if (is_sampling_event(event)) + return -EINVAL; + + if (event->attach_state & PERF_ATTACH_TASK) + return -EINVAL; + + if (event->attr.exclude_user || + event->attr.exclude_kernel || + event->attr.exclude_hv || + event->attr.exclude_idle || + event->attr.exclude_host || + event->attr.exclude_guest) + return -EINVAL; + + if (event->cpu < 0) + return -EINVAL; + + hw->config_base =3D event->attr.config; + + return 0; +} + +static enum hrtimer_restart stm32_ddr_pmu_poll(struct hrtimer *hrtimer) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D hrtimer_to_stm32_ddr_pmu(hrtimer)= ; + int i; + + for (i =3D 0; i < PMU_NR_COUNTERS; i++) + if (stm32_ddr_pmu->events[i]) + stm32_ddr_pmu_event_read(stm32_ddr_pmu->events[i]); + + hrtimer_forward_now(hrtimer, stm32_ddr_pmu->poll_period); + + return HRTIMER_RESTART; +} + +static ssize_t stm32_ddr_pmu_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *eattr; + + eattr =3D container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(buf, "config=3D0x%lx\n", (unsigned long)eattr->var); +} + +static ssize_t bandwidth_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D dev_get_drvdata(dev); + u64 r_bw, w_bw; + int ret; + + if (stm32_ddr_pmu->events_cnt[TIME_CNT]) { + r_bw =3D stm32_ddr_pmu_compute_bw(stm32_ddr_pmu, READ_CNT); + w_bw =3D stm32_ddr_pmu_compute_bw(stm32_ddr_pmu, WRITE_CNT); + + ret =3D snprintf(buf, PAGE_SIZE, STRING, + r_bw, w_bw, (r_bw + w_bw)); + } else { + ret =3D snprintf(buf, PAGE_SIZE, "No data available\n"); + } + + return ret; +} + +#define STM32_DDR_PMU_ATTR(_name, _func, _config) \ + (&((struct dev_ext_attribute[]) { \ + { __ATTR(_name, 0444, _func, NULL), (void *)_config } \ + })[0].attr.attr) + +#define STM32_DDR_PMU_EVENT_ATTR(_name, _config) \ + STM32_DDR_PMU_ATTR(_name, stm32_ddr_pmu_sysfs_show, \ + (unsigned long)_config) + +static struct attribute *stm32_ddr_pmu_event_attrs[] =3D { + STM32_DDR_PMU_EVENT_ATTR(read_cnt, READ_CNT), + STM32_DDR_PMU_EVENT_ATTR(write_cnt, WRITE_CNT), + STM32_DDR_PMU_EVENT_ATTR(activate_cnt, ACTIVATE_CNT), + STM32_DDR_PMU_EVENT_ATTR(idle_cnt, IDLE_CNT), + STM32_DDR_PMU_EVENT_ATTR(time_cnt, TIME_CNT), + NULL +}; + +static DEVICE_ATTR_RO(bandwidth); +static struct attribute *stm32_ddr_pmu_bandwidth_attrs[] =3D { + &dev_attr_bandwidth.attr, + NULL, +}; + +static struct attribute_group stm32_ddr_pmu_event_attrs_group =3D { + .name =3D "events", + .attrs =3D stm32_ddr_pmu_event_attrs, +}; + +static struct attribute_group stm32_ddr_pmu_bandwidth_attrs_group =3D { + .attrs =3D stm32_ddr_pmu_bandwidth_attrs, +}; + +static const struct attribute_group *stm32_ddr_pmu_attr_groups[] =3D { + &stm32_ddr_pmu_event_attrs_group, + &stm32_ddr_pmu_bandwidth_attrs_group, + NULL, +}; + +static int stm32_ddr_pmu_device_probe(struct platform_device *pdev) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu; + struct reset_control *rst; + struct resource *res; + int i, ret; + u32 val; + + stm32_ddr_pmu =3D devm_kzalloc(&pdev->dev, sizeof(struct stm32_ddr_pmu), + GFP_KERNEL); + if (!stm32_ddr_pmu) + return -ENOMEM; + platform_set_drvdata(pdev, stm32_ddr_pmu); + + res =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); + stm32_ddr_pmu->membase =3D devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(stm32_ddr_pmu->membase)) { + pr_warn("Unable to get STM32 DDR PMU membase\n"); + return PTR_ERR(stm32_ddr_pmu->membase); + } + + stm32_ddr_pmu->clk =3D devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(stm32_ddr_pmu->clk)) { + pr_warn("Unable to get STM32 DDR PMU clock\n"); + return PTR_ERR(stm32_ddr_pmu->clk); + } + + ret =3D clk_prepare_enable(stm32_ddr_pmu->clk); + if (ret) { + pr_warn("Unable to prepare STM32 DDR PMU clock\n"); + return ret; + } + + stm32_ddr_pmu->clk_ddr =3D devm_clk_get(&pdev->dev, "ddr"); + if (IS_ERR(stm32_ddr_pmu->clk_ddr)) { + pr_warn("Unable to get STM32 DDR clock\n"); + return PTR_ERR(stm32_ddr_pmu->clk_ddr); + } + stm32_ddr_pmu->clk_ddr_rate =3D clk_get_rate(stm32_ddr_pmu->clk_ddr); + stm32_ddr_pmu->clk_ddr_rate /=3D 1000000; + + stm32_ddr_pmu->poll_period =3D ms_to_ktime(POLL_MS); + hrtimer_init(&stm32_ddr_pmu->hrtimer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + stm32_ddr_pmu->hrtimer.function =3D stm32_ddr_pmu_poll; + spin_lock_init(&stm32_ddr_pmu->lock); + + for (i =3D 0; i < PMU_NR_COUNTERS; i++) { + stm32_ddr_pmu->events[i] =3D NULL; + stm32_ddr_pmu->events_cnt[i] =3D 0; + } + + val =3D readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_SID); + if (val !=3D SID_MAGIC_ID) + return -EINVAL; + + stm32_ddr_pmu->pmu =3D (struct pmu) { + .task_ctx_nr =3D perf_invalid_context, + .start =3D stm32_ddr_pmu_event_start, + .stop =3D stm32_ddr_pmu_event_stop, + .add =3D stm32_ddr_pmu_event_add, + .del =3D stm32_ddr_pmu_event_del, + .event_init =3D stm32_ddr_pmu_event_init, + .attr_groups =3D stm32_ddr_pmu_attr_groups, + }; + ret =3D perf_pmu_register(&stm32_ddr_pmu->pmu, "ddrperfm", -1); + if (ret) { + pr_warn("Unable to register STM32 DDR PMU\n"); + return ret; + } + + rst =3D devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + pr_info("stm32-ddr-pmu: probed (ID=3D0x%08x VER=3D0x%08x), DDR@%luMHz\n", + readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_ID), + readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_VER), + stm32_ddr_pmu->clk_ddr_rate); + + clk_disable(stm32_ddr_pmu->clk); + + return 0; +} + +static int stm32_ddr_pmu_device_remove(struct platform_device *pdev) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D platform_get_drvdata(pdev); + + perf_pmu_unregister(&stm32_ddr_pmu->pmu); + + return 0; +} + +static const struct of_device_id stm32_ddr_pmu_of_match[] =3D { + { .compatible =3D "st,stm32-ddr-pmu" }, + { }, +}; + +static struct platform_driver stm32_ddr_pmu_driver =3D { + .driver =3D { + .name =3D "stm32-ddr-pmu", + .of_match_table =3D of_match_ptr(stm32_ddr_pmu_of_match), + }, + .probe =3D stm32_ddr_pmu_device_probe, + .remove =3D stm32_ddr_pmu_device_remove, +}; + +module_platform_driver(stm32_ddr_pmu_driver); + +MODULE_DESCRIPTION("Perf driver for STM32 DDR performance monitor"); +MODULE_AUTHOR("Gerald Baeza "); +MODULE_LICENSE("GPL v2"); --=20 2.7.4 From mboxrd@z Thu Jan 1 00:00:00 1970 From: Gerald BAEZA Subject: [PATCH v2 3/5] perf: stm32: ddrperfm driver creation Date: Mon, 20 May 2019 15:27:17 +0000 Message-ID: <1558366019-24214-4-git-send-email-gerald.baeza@st.com> References: <1558366019-24214-1-git-send-email-gerald.baeza@st.com> Mime-Version: 1.0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable Return-path: In-Reply-To: <1558366019-24214-1-git-send-email-gerald.baeza@st.com> Content-Language: en-US Sender: linux-kernel-owner@vger.kernel.org To: "will.deacon@arm.com" , "mark.rutland@arm.com" , "robh+dt@kernel.org" , "mcoquelin.stm32@gmail.com" , Alexandre TORGUE , "corbet@lwn.net" , "linux@armlinux.org.uk" , "olof@lixom.net" , "horms+renesas@verge.net.au" , "arnd@arndb.de" Cc: "linux-arm-kernel@lists.infradead.org" , "devicetree@vger.kernel.org" , "linux-stm32@st-md-mailman.stormreply.com" , "linux-kernel@vger.kernel.org" , "linux-doc@vger.kernel.org" , Gerald BAEZA List-Id: devicetree@vger.kernel.org The DDRPERFM is the DDR Performance Monitor embedded in STM32MP1 SOC. This perf drivers supports the read, write, activate, idle and total time counters, described in the reference manual RM0436. A 'bandwidth' attribute is added in the 'ddrperfm' event_source in order to directly get the read and write bandwidths (in MB/s), from the last read, write and total time counters reading. This attribute is aside the 'events' attributes group because it is not a counter, as seen by perf tool. Signed-off-by: Gerald Baeza --- drivers/perf/Kconfig | 6 + drivers/perf/Makefile | 1 + drivers/perf/stm32_ddr_pmu.c | 512 +++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 519 insertions(+) create mode 100644 drivers/perf/stm32_ddr_pmu.c diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig index a94e586..9add8a7 100644 --- a/drivers/perf/Kconfig +++ b/drivers/perf/Kconfig @@ -105,6 +105,12 @@ config THUNDERX2_PMU The SoC has PMU support in its L3 cache controller (L3C) and in the DDR4 Memory Controller (DMC). =20 +config STM32_DDR_PMU + tristate "STM32 DDR PMU" + depends on MACH_STM32MP157 + help + Support for STM32 DDR performance monitor (DDRPERFM). + config XGENE_PMU depends on ARCH_XGENE bool "APM X-Gene SoC PMU" diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile index 3048994..fa64719 100644 --- a/drivers/perf/Makefile +++ b/drivers/perf/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_ARM_SMMU_V3_PMU) +=3D arm_smmuv3_pmu.o obj-$(CONFIG_HISI_PMU) +=3D hisilicon/ obj-$(CONFIG_QCOM_L2_PMU) +=3D qcom_l2_pmu.o obj-$(CONFIG_QCOM_L3_PMU) +=3D qcom_l3_pmu.o +obj-$(CONFIG_STM32_DDR_PMU) +=3D stm32_ddr_pmu.o obj-$(CONFIG_THUNDERX2_PMU) +=3D thunderx2_pmu.o obj-$(CONFIG_XGENE_PMU) +=3D xgene_pmu.o obj-$(CONFIG_ARM_SPE_PMU) +=3D arm_spe_pmu.o diff --git a/drivers/perf/stm32_ddr_pmu.c b/drivers/perf/stm32_ddr_pmu.c new file mode 100644 index 0000000..ae4a813 --- /dev/null +++ b/drivers/perf/stm32_ddr_pmu.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is the STM32 DDR performance monitor (DDRPERFM) driver + * + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + * Author: Gerald Baeza + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define POLL_MS 4000 /* The counter roll over after ~8s @533MHz */ +#define WORD_LENGTH 4 /* Bytes */ +#define BURST_LENGTH 8 /* Words */ + +#define DDRPERFM_CTL 0x000 +#define DDRPERFM_CFG 0x004 +#define DDRPERFM_STATUS 0x008 +#define DDRPERFM_CCR 0x00C +#define DDRPERFM_IER 0x010 +#define DDRPERFM_ISR 0x014 +#define DDRPERFM_ICR 0x018 +#define DDRPERFM_TCNT 0x020 +#define DDRPERFM_CNT(X) (0x030 + 8 * (X)) +#define DDRPERFM_HWCFG 0x3F0 +#define DDRPERFM_VER 0x3F4 +#define DDRPERFM_ID 0x3F8 +#define DDRPERFM_SID 0x3FC + +#define CTL_START 0x00000001 +#define CTL_STOP 0x00000002 +#define CCR_CLEAR_ALL 0x8000000F +#define SID_MAGIC_ID 0xA3C5DD01 + +#define STRING "Read =3D %llu, Write =3D %llu, Read & Write =3D %llu (MB/s= )\n" + +enum { + READ_CNT, + WRITE_CNT, + ACTIVATE_CNT, + IDLE_CNT, + TIME_CNT, + PMU_NR_COUNTERS +}; + +struct stm32_ddr_pmu { + struct pmu pmu; + void __iomem *membase; + struct clk *clk; + struct clk *clk_ddr; + unsigned long clk_ddr_rate; + struct hrtimer hrtimer; + ktime_t poll_period; + spinlock_t lock; /* for shared registers access */ + struct perf_event *events[PMU_NR_COUNTERS]; + u64 events_cnt[PMU_NR_COUNTERS]; +}; + +static inline struct stm32_ddr_pmu *pmu_to_stm32_ddr_pmu(struct pmu *p) +{ + return container_of(p, struct stm32_ddr_pmu, pmu); +} + +static inline struct stm32_ddr_pmu *hrtimer_to_stm32_ddr_pmu(struct hrtime= r *h) +{ + return container_of(h, struct stm32_ddr_pmu, hrtimer); +} + +static u64 stm32_ddr_pmu_compute_bw(struct stm32_ddr_pmu *stm32_ddr_pmu, + int counter) +{ + u64 bw =3D stm32_ddr_pmu->events_cnt[counter], tmp; + u64 div =3D stm32_ddr_pmu->events_cnt[TIME_CNT]; + u32 prediv =3D 1, premul =3D 1; + + if (bw && div) { + /* Maximize the dividend into 64 bits */ + while ((bw < 0x8000000000000000ULL) && + (premul < 0x40000000UL)) { + bw =3D bw << 1; + premul *=3D 2; + } + /* Minimize the dividor to fit in 32 bits */ + while ((div > 0xffffffffUL) && (prediv < 0x40000000UL)) { + div =3D div >> 1; + prediv *=3D 2; + } + /* Divide counter per time and multiply per DDR settings */ + do_div(bw, div); + tmp =3D bw * BURST_LENGTH * WORD_LENGTH; + tmp *=3D stm32_ddr_pmu->clk_ddr_rate; + if (tmp < bw) + goto error; + bw =3D tmp; + /* Cancel the prediv and premul factors */ + while (prediv > 1) { + bw =3D bw >> 1; + prediv /=3D 2; + } + while (premul > 1) { + bw =3D bw >> 1; + premul /=3D 2; + } + /* Convert MHz to Hz and B to MB, to finally get MB/s */ + tmp =3D bw * 1000000; + if (tmp < bw) + goto error; + bw =3D tmp; + premul =3D 1024 * 1024; + while (premul > 1) { + bw =3D bw >> 1; + premul /=3D 2; + } + } + return bw; + +error: + pr_warn("stm32-ddr-pmu: overflow detected\n"); + return 0; +} + +static void stm32_ddr_pmu_event_configure(struct perf_event *event) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long lock_flags, config_base =3D event->hw.config_base; + u32 val; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + if (config_base < TIME_CNT) { + val =3D readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_CFG); + val |=3D (1 << config_base); + writel_relaxed(val, stm32_ddr_pmu->membase + DDRPERFM_CFG); + } + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); +} + +static void stm32_ddr_pmu_event_read(struct perf_event *event) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long flags, config_base =3D event->hw.config_base; + struct hw_perf_event *hw =3D &event->hw; + u64 prev_count, new_count, mask; + u32 val, offset, bit; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, flags); + + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + if (config_base =3D=3D TIME_CNT) { + offset =3D DDRPERFM_TCNT; + bit =3D 1 << 31; + } else { + offset =3D DDRPERFM_CNT(config_base); + bit =3D 1 << config_base; + } + val =3D readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_STATUS); + if (val & bit) + pr_warn("stm32_ddr_pmu hardware overflow\n"); + val =3D readl_relaxed(stm32_ddr_pmu->membase + offset); + writel_relaxed(bit, stm32_ddr_pmu->membase + DDRPERFM_CCR); + writel_relaxed(CTL_START, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + do { + prev_count =3D local64_read(&hw->prev_count); + new_count =3D prev_count + val; + } while (local64_xchg(&hw->prev_count, new_count) !=3D prev_count); + + mask =3D GENMASK_ULL(31, 0); + local64_add(val & mask, &event->count); + + if (new_count < prev_count) + pr_warn("STM32 DDR PMU counter saturated\n"); + + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, flags); +} + +static void stm32_ddr_pmu_event_start(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + struct hw_perf_event *hw =3D &event->hw; + unsigned long lock_flags; + + if (WARN_ON_ONCE(!(hw->state & PERF_HES_STOPPED))) + return; + + if (flags & PERF_EF_RELOAD) + WARN_ON_ONCE(!(hw->state & PERF_HES_UPTODATE)); + + stm32_ddr_pmu_event_configure(event); + + /* Clear all counters to synchronize them, then start */ + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CCR_CLEAR_ALL, stm32_ddr_pmu->membase + DDRPERFM_CCR); + writel_relaxed(CTL_START, stm32_ddr_pmu->membase + DDRPERFM_CTL); + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); + + hw->state =3D 0; +} + +static void stm32_ddr_pmu_event_stop(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long lock_flags, config_base =3D event->hw.config_base; + struct hw_perf_event *hw =3D &event->hw; + u32 val, bit; + + if (WARN_ON_ONCE(hw->state & PERF_HES_STOPPED)) + return; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + if (config_base =3D=3D TIME_CNT) + bit =3D 1 << 31; + else + bit =3D 1 << config_base; + writel_relaxed(bit, stm32_ddr_pmu->membase + DDRPERFM_CCR); + if (config_base < TIME_CNT) { + val =3D readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_CFG); + val &=3D ~bit; + writel_relaxed(val, stm32_ddr_pmu->membase + DDRPERFM_CFG); + } + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); + + hw->state |=3D PERF_HES_STOPPED; + + if (flags & PERF_EF_UPDATE) { + stm32_ddr_pmu_event_read(event); + hw->state |=3D PERF_HES_UPTODATE; + } +} + +static int stm32_ddr_pmu_event_add(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long config_base =3D event->hw.config_base; + struct hw_perf_event *hw =3D &event->hw; + + stm32_ddr_pmu->events_cnt[config_base] =3D 0; + stm32_ddr_pmu->events[config_base] =3D event; + + clk_enable(stm32_ddr_pmu->clk); + hrtimer_start(&stm32_ddr_pmu->hrtimer, stm32_ddr_pmu->poll_period, + HRTIMER_MODE_REL); + + stm32_ddr_pmu_event_configure(event); + + hw->state =3D PERF_HES_STOPPED | PERF_HES_UPTODATE; + + if (flags & PERF_EF_START) + stm32_ddr_pmu_event_start(event, 0); + + return 0; +} + +static void stm32_ddr_pmu_event_del(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long config_base =3D event->hw.config_base; + bool stop =3D true; + int i; + + stm32_ddr_pmu_event_stop(event, PERF_EF_UPDATE); + + stm32_ddr_pmu->events_cnt[config_base] +=3D local64_read(&event->count); + stm32_ddr_pmu->events[config_base] =3D NULL; + + for (i =3D 0; i < PMU_NR_COUNTERS; i++) + if (stm32_ddr_pmu->events[i]) + stop =3D false; + if (stop) + hrtimer_cancel(&stm32_ddr_pmu->hrtimer); + + clk_disable(stm32_ddr_pmu->clk); +} + +static int stm32_ddr_pmu_event_init(struct perf_event *event) +{ + struct hw_perf_event *hw =3D &event->hw; + + if (is_sampling_event(event)) + return -EINVAL; + + if (event->attach_state & PERF_ATTACH_TASK) + return -EINVAL; + + if (event->attr.exclude_user || + event->attr.exclude_kernel || + event->attr.exclude_hv || + event->attr.exclude_idle || + event->attr.exclude_host || + event->attr.exclude_guest) + return -EINVAL; + + if (event->cpu < 0) + return -EINVAL; + + hw->config_base =3D event->attr.config; + + return 0; +} + +static enum hrtimer_restart stm32_ddr_pmu_poll(struct hrtimer *hrtimer) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D hrtimer_to_stm32_ddr_pmu(hrtimer)= ; + int i; + + for (i =3D 0; i < PMU_NR_COUNTERS; i++) + if (stm32_ddr_pmu->events[i]) + stm32_ddr_pmu_event_read(stm32_ddr_pmu->events[i]); + + hrtimer_forward_now(hrtimer, stm32_ddr_pmu->poll_period); + + return HRTIMER_RESTART; +} + +static ssize_t stm32_ddr_pmu_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *eattr; + + eattr =3D container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(buf, "config=3D0x%lx\n", (unsigned long)eattr->var); +} + +static ssize_t bandwidth_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D dev_get_drvdata(dev); + u64 r_bw, w_bw; + int ret; + + if (stm32_ddr_pmu->events_cnt[TIME_CNT]) { + r_bw =3D stm32_ddr_pmu_compute_bw(stm32_ddr_pmu, READ_CNT); + w_bw =3D stm32_ddr_pmu_compute_bw(stm32_ddr_pmu, WRITE_CNT); + + ret =3D snprintf(buf, PAGE_SIZE, STRING, + r_bw, w_bw, (r_bw + w_bw)); + } else { + ret =3D snprintf(buf, PAGE_SIZE, "No data available\n"); + } + + return ret; +} + +#define STM32_DDR_PMU_ATTR(_name, _func, _config) \ + (&((struct dev_ext_attribute[]) { \ + { __ATTR(_name, 0444, _func, NULL), (void *)_config } \ + })[0].attr.attr) + +#define STM32_DDR_PMU_EVENT_ATTR(_name, _config) \ + STM32_DDR_PMU_ATTR(_name, stm32_ddr_pmu_sysfs_show, \ + (unsigned long)_config) + +static struct attribute *stm32_ddr_pmu_event_attrs[] =3D { + STM32_DDR_PMU_EVENT_ATTR(read_cnt, READ_CNT), + STM32_DDR_PMU_EVENT_ATTR(write_cnt, WRITE_CNT), + STM32_DDR_PMU_EVENT_ATTR(activate_cnt, ACTIVATE_CNT), + STM32_DDR_PMU_EVENT_ATTR(idle_cnt, IDLE_CNT), + STM32_DDR_PMU_EVENT_ATTR(time_cnt, TIME_CNT), + NULL +}; + +static DEVICE_ATTR_RO(bandwidth); +static struct attribute *stm32_ddr_pmu_bandwidth_attrs[] =3D { + &dev_attr_bandwidth.attr, + NULL, +}; + +static struct attribute_group stm32_ddr_pmu_event_attrs_group =3D { + .name =3D "events", + .attrs =3D stm32_ddr_pmu_event_attrs, +}; + +static struct attribute_group stm32_ddr_pmu_bandwidth_attrs_group =3D { + .attrs =3D stm32_ddr_pmu_bandwidth_attrs, +}; + +static const struct attribute_group *stm32_ddr_pmu_attr_groups[] =3D { + &stm32_ddr_pmu_event_attrs_group, + &stm32_ddr_pmu_bandwidth_attrs_group, + NULL, +}; + +static int stm32_ddr_pmu_device_probe(struct platform_device *pdev) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu; + struct reset_control *rst; + struct resource *res; + int i, ret; + u32 val; + + stm32_ddr_pmu =3D devm_kzalloc(&pdev->dev, sizeof(struct stm32_ddr_pmu), + GFP_KERNEL); + if (!stm32_ddr_pmu) + return -ENOMEM; + platform_set_drvdata(pdev, stm32_ddr_pmu); + + res =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); + stm32_ddr_pmu->membase =3D devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(stm32_ddr_pmu->membase)) { + pr_warn("Unable to get STM32 DDR PMU membase\n"); + return PTR_ERR(stm32_ddr_pmu->membase); + } + + stm32_ddr_pmu->clk =3D devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(stm32_ddr_pmu->clk)) { + pr_warn("Unable to get STM32 DDR PMU clock\n"); + return PTR_ERR(stm32_ddr_pmu->clk); + } + + ret =3D clk_prepare_enable(stm32_ddr_pmu->clk); + if (ret) { + pr_warn("Unable to prepare STM32 DDR PMU clock\n"); + return ret; + } + + stm32_ddr_pmu->clk_ddr =3D devm_clk_get(&pdev->dev, "ddr"); + if (IS_ERR(stm32_ddr_pmu->clk_ddr)) { + pr_warn("Unable to get STM32 DDR clock\n"); + return PTR_ERR(stm32_ddr_pmu->clk_ddr); + } + stm32_ddr_pmu->clk_ddr_rate =3D clk_get_rate(stm32_ddr_pmu->clk_ddr); + stm32_ddr_pmu->clk_ddr_rate /=3D 1000000; + + stm32_ddr_pmu->poll_period =3D ms_to_ktime(POLL_MS); + hrtimer_init(&stm32_ddr_pmu->hrtimer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + stm32_ddr_pmu->hrtimer.function =3D stm32_ddr_pmu_poll; + spin_lock_init(&stm32_ddr_pmu->lock); + + for (i =3D 0; i < PMU_NR_COUNTERS; i++) { + stm32_ddr_pmu->events[i] =3D NULL; + stm32_ddr_pmu->events_cnt[i] =3D 0; + } + + val =3D readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_SID); + if (val !=3D SID_MAGIC_ID) + return -EINVAL; + + stm32_ddr_pmu->pmu =3D (struct pmu) { + .task_ctx_nr =3D perf_invalid_context, + .start =3D stm32_ddr_pmu_event_start, + .stop =3D stm32_ddr_pmu_event_stop, + .add =3D stm32_ddr_pmu_event_add, + .del =3D stm32_ddr_pmu_event_del, + .event_init =3D stm32_ddr_pmu_event_init, + .attr_groups =3D stm32_ddr_pmu_attr_groups, + }; + ret =3D perf_pmu_register(&stm32_ddr_pmu->pmu, "ddrperfm", -1); + if (ret) { + pr_warn("Unable to register STM32 DDR PMU\n"); + return ret; + } + + rst =3D devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + pr_info("stm32-ddr-pmu: probed (ID=3D0x%08x VER=3D0x%08x), DDR@%luMHz\n", + readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_ID), + readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_VER), + stm32_ddr_pmu->clk_ddr_rate); + + clk_disable(stm32_ddr_pmu->clk); + + return 0; +} + +static int stm32_ddr_pmu_device_remove(struct platform_device *pdev) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu =3D platform_get_drvdata(pdev); + + perf_pmu_unregister(&stm32_ddr_pmu->pmu); + + return 0; +} + +static const struct of_device_id stm32_ddr_pmu_of_match[] =3D { + { .compatible =3D "st,stm32-ddr-pmu" }, + { }, +}; + +static struct platform_driver stm32_ddr_pmu_driver =3D { + .driver =3D { + .name =3D "stm32-ddr-pmu", + .of_match_table =3D of_match_ptr(stm32_ddr_pmu_of_match), + }, + .probe =3D stm32_ddr_pmu_device_probe, + .remove =3D stm32_ddr_pmu_device_remove, +}; + +module_platform_driver(stm32_ddr_pmu_driver); + +MODULE_DESCRIPTION("Perf driver for STM32 DDR performance monitor"); +MODULE_AUTHOR("Gerald Baeza "); +MODULE_LICENSE("GPL v2"); --=20 2.7.4 From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.0 required=3.0 tests=DKIMWL_WL_HIGH,DKIM_SIGNED, DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_PATCH,MAILING_LIST_MULTI, SIGNED_OFF_BY,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 699FCC04AAC for ; Mon, 20 May 2019 15:28:40 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 377D020863 for ; Mon, 20 May 2019 15:28:40 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=lists.infradead.org header.i=@lists.infradead.org header.b="oFH8ypF5"; dkim=fail reason="signature verification failed" (2048-bit key) header.d=st.com header.i=@st.com header.b="aO8HpXI7" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 377D020863 Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=st.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+infradead-linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:In-Reply-To:References: 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=eIcyVWeMowub80EEW75ePlXgaetfC68wuM1Z2V18Gok=; b=oFH8ypF5E9cgFT Q7RZaO0SxhLSj80VcR3BlVl0FRTBy1nVnqSILZyATacAHbEj0jvRy/vIplsG2a4ffgCohOx/Zez9m ExVSqaQNX/NI6jp8PiycYcCXazH9Dk57evlLv1Mv/yXD5HR9BZU9VTL93oF2IYM0TkgN0ESEQ+r6+ sVkGDdr3ytbGkZUAsH1HZOqG0Ds++mkZwAbNlnpO4mN/9TFojWBkszs/cQBmEqNr1sdq5RcB45ia5 jnCbqf5IQPsouW70VH8+sDuZrFQ2PxGYovqBYgxbbC4M+lS/haGPp4NBGoiwX9ewiPCGr3BtcukNH iaeaCvKP/bkZhbmdk2lg==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.90_1 #2 (Red Hat Linux)) id 1hSkDK-0000Yi-WE; Mon, 20 May 2019 15:28:35 +0000 Received: from mx07-00178001.pphosted.com ([62.209.51.94]) by bombadil.infradead.org with esmtps (Exim 4.90_1 #2 (Red Hat Linux)) id 1hSkCS-0007rf-UN for linux-arm-kernel@lists.infradead.org; Mon, 20 May 2019 15:27:43 +0000 Received: from pps.filterd (m0046668.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.16.0.27/8.16.0.27) with SMTP id x4KFLUFE002106; Mon, 20 May 2019 17:27:19 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=st.com; h=from : to : cc : subject : date : message-id : references : in-reply-to : content-type : content-transfer-encoding : mime-version; s=STMicroelectronics; bh=Z9x1RsoFBv1VevDzyNrpT9CEsTNBt+qZC3x8CkrHJ1g=; b=aO8HpXI7QMutUlzBjnezfp0AAE44P8S1B2o+x1R4osrQgRTvjMVM/jn7uaxOH2Pka/TF 4VR/tI5QZ8dxEuxhzPJrJle75+/mgcZEHXC7wbvTQpCETJ0X978ff9Ir6FsaoJO6lyWC xp2EF9NmBa5bi4Rie/PNYUfPKOCtyz1/56EqcqGl1DBWBrPG9TwG36VTPNwU4cCtbA2q u2fZiov4t15sYRUsx3LjBXqjBI7A0vOo1JBr8Y/2JJjA2uwZuIZIYqeUSkoOAAbuAieh /OOZfObOeXBuqqAk6ITCKYNWyao7wnR3ebx0e0dWUxSTAg2S65hnKE0BTIVWV0PUHLdf Dw== Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-00178001.pphosted.com with ESMTP id 2sj7h0mb1x-1 (version=TLSv1 cipher=ECDHE-RSA-AES256-SHA bits=256 verify=NOT); Mon, 20 May 2019 17:27:19 +0200 Received: from zeta.dmz-eu.st.com (zeta.dmz-eu.st.com [164.129.230.9]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 30CA73D; Mon, 20 May 2019 15:27:18 +0000 (GMT) Received: from Webmail-eu.st.com (sfhdag3node3.st.com [10.75.127.9]) by zeta.dmz-eu.st.com (STMicroelectronics) with ESMTP id F082C2BAC; Mon, 20 May 2019 15:27:17 +0000 (GMT) Received: from SFHDAG5NODE1.st.com (10.75.127.13) by SFHDAG3NODE3.st.com (10.75.127.9) with Microsoft SMTP Server (TLS) id 15.0.1347.2; Mon, 20 May 2019 17:27:17 +0200 Received: from SFHDAG5NODE1.st.com ([fe80::cc53:528c:36c8:95f6]) by SFHDAG5NODE1.st.com ([fe80::cc53:528c:36c8:95f6%20]) with mapi id 15.00.1347.000; Mon, 20 May 2019 17:27:17 +0200 From: Gerald BAEZA To: "will.deacon@arm.com" , "mark.rutland@arm.com" , "robh+dt@kernel.org" , "mcoquelin.stm32@gmail.com" , Alexandre TORGUE , "corbet@lwn.net" , "linux@armlinux.org.uk" , "olof@lixom.net" , "horms+renesas@verge.net.au" , "arnd@arndb.de" Subject: [PATCH v2 3/5] perf: stm32: ddrperfm driver creation Thread-Topic: [PATCH v2 3/5] perf: stm32: ddrperfm driver creation Thread-Index: AQHVDyCCI5Cs89crU0SFN7k5vdHanA== Date: Mon, 20 May 2019 15:27:17 +0000 Message-ID: <1558366019-24214-4-git-send-email-gerald.baeza@st.com> References: <1558366019-24214-1-git-send-email-gerald.baeza@st.com> In-Reply-To: <1558366019-24214-1-git-send-email-gerald.baeza@st.com> Accept-Language: fr-FR, en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-ms-exchange-messagesentrepresentingtype: 1 x-ms-exchange-transport-fromentityheader: Hosted x-originating-ip: [10.75.127.45] MIME-Version: 1.0 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:, , definitions=2019-05-20_07:, , signatures=0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20190520_082741_271155_327A1135 X-CRM114-Status: GOOD ( 22.45 ) 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: "devicetree@vger.kernel.org" , "linux-doc@vger.kernel.org" , "linux-kernel@vger.kernel.org" , Gerald BAEZA , "linux-stm32@st-md-mailman.stormreply.com" , "linux-arm-kernel@lists.infradead.org" Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+infradead-linux-arm-kernel=archiver.kernel.org@lists.infradead.org The DDRPERFM is the DDR Performance Monitor embedded in STM32MP1 SOC. This perf drivers supports the read, write, activate, idle and total time counters, described in the reference manual RM0436. A 'bandwidth' attribute is added in the 'ddrperfm' event_source in order to directly get the read and write bandwidths (in MB/s), from the last read, write and total time counters reading. This attribute is aside the 'events' attributes group because it is not a counter, as seen by perf tool. Signed-off-by: Gerald Baeza --- drivers/perf/Kconfig | 6 + drivers/perf/Makefile | 1 + drivers/perf/stm32_ddr_pmu.c | 512 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 519 insertions(+) create mode 100644 drivers/perf/stm32_ddr_pmu.c diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig index a94e586..9add8a7 100644 --- a/drivers/perf/Kconfig +++ b/drivers/perf/Kconfig @@ -105,6 +105,12 @@ config THUNDERX2_PMU The SoC has PMU support in its L3 cache controller (L3C) and in the DDR4 Memory Controller (DMC). +config STM32_DDR_PMU + tristate "STM32 DDR PMU" + depends on MACH_STM32MP157 + help + Support for STM32 DDR performance monitor (DDRPERFM). + config XGENE_PMU depends on ARCH_XGENE bool "APM X-Gene SoC PMU" diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile index 3048994..fa64719 100644 --- a/drivers/perf/Makefile +++ b/drivers/perf/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_ARM_SMMU_V3_PMU) += arm_smmuv3_pmu.o obj-$(CONFIG_HISI_PMU) += hisilicon/ obj-$(CONFIG_QCOM_L2_PMU) += qcom_l2_pmu.o obj-$(CONFIG_QCOM_L3_PMU) += qcom_l3_pmu.o +obj-$(CONFIG_STM32_DDR_PMU) += stm32_ddr_pmu.o obj-$(CONFIG_THUNDERX2_PMU) += thunderx2_pmu.o obj-$(CONFIG_XGENE_PMU) += xgene_pmu.o obj-$(CONFIG_ARM_SPE_PMU) += arm_spe_pmu.o diff --git a/drivers/perf/stm32_ddr_pmu.c b/drivers/perf/stm32_ddr_pmu.c new file mode 100644 index 0000000..ae4a813 --- /dev/null +++ b/drivers/perf/stm32_ddr_pmu.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file is the STM32 DDR performance monitor (DDRPERFM) driver + * + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + * Author: Gerald Baeza + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define POLL_MS 4000 /* The counter roll over after ~8s @533MHz */ +#define WORD_LENGTH 4 /* Bytes */ +#define BURST_LENGTH 8 /* Words */ + +#define DDRPERFM_CTL 0x000 +#define DDRPERFM_CFG 0x004 +#define DDRPERFM_STATUS 0x008 +#define DDRPERFM_CCR 0x00C +#define DDRPERFM_IER 0x010 +#define DDRPERFM_ISR 0x014 +#define DDRPERFM_ICR 0x018 +#define DDRPERFM_TCNT 0x020 +#define DDRPERFM_CNT(X) (0x030 + 8 * (X)) +#define DDRPERFM_HWCFG 0x3F0 +#define DDRPERFM_VER 0x3F4 +#define DDRPERFM_ID 0x3F8 +#define DDRPERFM_SID 0x3FC + +#define CTL_START 0x00000001 +#define CTL_STOP 0x00000002 +#define CCR_CLEAR_ALL 0x8000000F +#define SID_MAGIC_ID 0xA3C5DD01 + +#define STRING "Read = %llu, Write = %llu, Read & Write = %llu (MB/s)\n" + +enum { + READ_CNT, + WRITE_CNT, + ACTIVATE_CNT, + IDLE_CNT, + TIME_CNT, + PMU_NR_COUNTERS +}; + +struct stm32_ddr_pmu { + struct pmu pmu; + void __iomem *membase; + struct clk *clk; + struct clk *clk_ddr; + unsigned long clk_ddr_rate; + struct hrtimer hrtimer; + ktime_t poll_period; + spinlock_t lock; /* for shared registers access */ + struct perf_event *events[PMU_NR_COUNTERS]; + u64 events_cnt[PMU_NR_COUNTERS]; +}; + +static inline struct stm32_ddr_pmu *pmu_to_stm32_ddr_pmu(struct pmu *p) +{ + return container_of(p, struct stm32_ddr_pmu, pmu); +} + +static inline struct stm32_ddr_pmu *hrtimer_to_stm32_ddr_pmu(struct hrtimer *h) +{ + return container_of(h, struct stm32_ddr_pmu, hrtimer); +} + +static u64 stm32_ddr_pmu_compute_bw(struct stm32_ddr_pmu *stm32_ddr_pmu, + int counter) +{ + u64 bw = stm32_ddr_pmu->events_cnt[counter], tmp; + u64 div = stm32_ddr_pmu->events_cnt[TIME_CNT]; + u32 prediv = 1, premul = 1; + + if (bw && div) { + /* Maximize the dividend into 64 bits */ + while ((bw < 0x8000000000000000ULL) && + (premul < 0x40000000UL)) { + bw = bw << 1; + premul *= 2; + } + /* Minimize the dividor to fit in 32 bits */ + while ((div > 0xffffffffUL) && (prediv < 0x40000000UL)) { + div = div >> 1; + prediv *= 2; + } + /* Divide counter per time and multiply per DDR settings */ + do_div(bw, div); + tmp = bw * BURST_LENGTH * WORD_LENGTH; + tmp *= stm32_ddr_pmu->clk_ddr_rate; + if (tmp < bw) + goto error; + bw = tmp; + /* Cancel the prediv and premul factors */ + while (prediv > 1) { + bw = bw >> 1; + prediv /= 2; + } + while (premul > 1) { + bw = bw >> 1; + premul /= 2; + } + /* Convert MHz to Hz and B to MB, to finally get MB/s */ + tmp = bw * 1000000; + if (tmp < bw) + goto error; + bw = tmp; + premul = 1024 * 1024; + while (premul > 1) { + bw = bw >> 1; + premul /= 2; + } + } + return bw; + +error: + pr_warn("stm32-ddr-pmu: overflow detected\n"); + return 0; +} + +static void stm32_ddr_pmu_event_configure(struct perf_event *event) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long lock_flags, config_base = event->hw.config_base; + u32 val; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + if (config_base < TIME_CNT) { + val = readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_CFG); + val |= (1 << config_base); + writel_relaxed(val, stm32_ddr_pmu->membase + DDRPERFM_CFG); + } + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); +} + +static void stm32_ddr_pmu_event_read(struct perf_event *event) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long flags, config_base = event->hw.config_base; + struct hw_perf_event *hw = &event->hw; + u64 prev_count, new_count, mask; + u32 val, offset, bit; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, flags); + + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + if (config_base == TIME_CNT) { + offset = DDRPERFM_TCNT; + bit = 1 << 31; + } else { + offset = DDRPERFM_CNT(config_base); + bit = 1 << config_base; + } + val = readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_STATUS); + if (val & bit) + pr_warn("stm32_ddr_pmu hardware overflow\n"); + val = readl_relaxed(stm32_ddr_pmu->membase + offset); + writel_relaxed(bit, stm32_ddr_pmu->membase + DDRPERFM_CCR); + writel_relaxed(CTL_START, stm32_ddr_pmu->membase + DDRPERFM_CTL); + + do { + prev_count = local64_read(&hw->prev_count); + new_count = prev_count + val; + } while (local64_xchg(&hw->prev_count, new_count) != prev_count); + + mask = GENMASK_ULL(31, 0); + local64_add(val & mask, &event->count); + + if (new_count < prev_count) + pr_warn("STM32 DDR PMU counter saturated\n"); + + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, flags); +} + +static void stm32_ddr_pmu_event_start(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = pmu_to_stm32_ddr_pmu(event->pmu); + struct hw_perf_event *hw = &event->hw; + unsigned long lock_flags; + + if (WARN_ON_ONCE(!(hw->state & PERF_HES_STOPPED))) + return; + + if (flags & PERF_EF_RELOAD) + WARN_ON_ONCE(!(hw->state & PERF_HES_UPTODATE)); + + stm32_ddr_pmu_event_configure(event); + + /* Clear all counters to synchronize them, then start */ + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CCR_CLEAR_ALL, stm32_ddr_pmu->membase + DDRPERFM_CCR); + writel_relaxed(CTL_START, stm32_ddr_pmu->membase + DDRPERFM_CTL); + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); + + hw->state = 0; +} + +static void stm32_ddr_pmu_event_stop(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long lock_flags, config_base = event->hw.config_base; + struct hw_perf_event *hw = &event->hw; + u32 val, bit; + + if (WARN_ON_ONCE(hw->state & PERF_HES_STOPPED)) + return; + + spin_lock_irqsave(&stm32_ddr_pmu->lock, lock_flags); + writel_relaxed(CTL_STOP, stm32_ddr_pmu->membase + DDRPERFM_CTL); + if (config_base == TIME_CNT) + bit = 1 << 31; + else + bit = 1 << config_base; + writel_relaxed(bit, stm32_ddr_pmu->membase + DDRPERFM_CCR); + if (config_base < TIME_CNT) { + val = readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_CFG); + val &= ~bit; + writel_relaxed(val, stm32_ddr_pmu->membase + DDRPERFM_CFG); + } + spin_unlock_irqrestore(&stm32_ddr_pmu->lock, lock_flags); + + hw->state |= PERF_HES_STOPPED; + + if (flags & PERF_EF_UPDATE) { + stm32_ddr_pmu_event_read(event); + hw->state |= PERF_HES_UPTODATE; + } +} + +static int stm32_ddr_pmu_event_add(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long config_base = event->hw.config_base; + struct hw_perf_event *hw = &event->hw; + + stm32_ddr_pmu->events_cnt[config_base] = 0; + stm32_ddr_pmu->events[config_base] = event; + + clk_enable(stm32_ddr_pmu->clk); + hrtimer_start(&stm32_ddr_pmu->hrtimer, stm32_ddr_pmu->poll_period, + HRTIMER_MODE_REL); + + stm32_ddr_pmu_event_configure(event); + + hw->state = PERF_HES_STOPPED | PERF_HES_UPTODATE; + + if (flags & PERF_EF_START) + stm32_ddr_pmu_event_start(event, 0); + + return 0; +} + +static void stm32_ddr_pmu_event_del(struct perf_event *event, int flags) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = pmu_to_stm32_ddr_pmu(event->pmu); + unsigned long config_base = event->hw.config_base; + bool stop = true; + int i; + + stm32_ddr_pmu_event_stop(event, PERF_EF_UPDATE); + + stm32_ddr_pmu->events_cnt[config_base] += local64_read(&event->count); + stm32_ddr_pmu->events[config_base] = NULL; + + for (i = 0; i < PMU_NR_COUNTERS; i++) + if (stm32_ddr_pmu->events[i]) + stop = false; + if (stop) + hrtimer_cancel(&stm32_ddr_pmu->hrtimer); + + clk_disable(stm32_ddr_pmu->clk); +} + +static int stm32_ddr_pmu_event_init(struct perf_event *event) +{ + struct hw_perf_event *hw = &event->hw; + + if (is_sampling_event(event)) + return -EINVAL; + + if (event->attach_state & PERF_ATTACH_TASK) + return -EINVAL; + + if (event->attr.exclude_user || + event->attr.exclude_kernel || + event->attr.exclude_hv || + event->attr.exclude_idle || + event->attr.exclude_host || + event->attr.exclude_guest) + return -EINVAL; + + if (event->cpu < 0) + return -EINVAL; + + hw->config_base = event->attr.config; + + return 0; +} + +static enum hrtimer_restart stm32_ddr_pmu_poll(struct hrtimer *hrtimer) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = hrtimer_to_stm32_ddr_pmu(hrtimer); + int i; + + for (i = 0; i < PMU_NR_COUNTERS; i++) + if (stm32_ddr_pmu->events[i]) + stm32_ddr_pmu_event_read(stm32_ddr_pmu->events[i]); + + hrtimer_forward_now(hrtimer, stm32_ddr_pmu->poll_period); + + return HRTIMER_RESTART; +} + +static ssize_t stm32_ddr_pmu_sysfs_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *eattr; + + eattr = container_of(attr, struct dev_ext_attribute, attr); + + return sprintf(buf, "config=0x%lx\n", (unsigned long)eattr->var); +} + +static ssize_t bandwidth_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = dev_get_drvdata(dev); + u64 r_bw, w_bw; + int ret; + + if (stm32_ddr_pmu->events_cnt[TIME_CNT]) { + r_bw = stm32_ddr_pmu_compute_bw(stm32_ddr_pmu, READ_CNT); + w_bw = stm32_ddr_pmu_compute_bw(stm32_ddr_pmu, WRITE_CNT); + + ret = snprintf(buf, PAGE_SIZE, STRING, + r_bw, w_bw, (r_bw + w_bw)); + } else { + ret = snprintf(buf, PAGE_SIZE, "No data available\n"); + } + + return ret; +} + +#define STM32_DDR_PMU_ATTR(_name, _func, _config) \ + (&((struct dev_ext_attribute[]) { \ + { __ATTR(_name, 0444, _func, NULL), (void *)_config } \ + })[0].attr.attr) + +#define STM32_DDR_PMU_EVENT_ATTR(_name, _config) \ + STM32_DDR_PMU_ATTR(_name, stm32_ddr_pmu_sysfs_show, \ + (unsigned long)_config) + +static struct attribute *stm32_ddr_pmu_event_attrs[] = { + STM32_DDR_PMU_EVENT_ATTR(read_cnt, READ_CNT), + STM32_DDR_PMU_EVENT_ATTR(write_cnt, WRITE_CNT), + STM32_DDR_PMU_EVENT_ATTR(activate_cnt, ACTIVATE_CNT), + STM32_DDR_PMU_EVENT_ATTR(idle_cnt, IDLE_CNT), + STM32_DDR_PMU_EVENT_ATTR(time_cnt, TIME_CNT), + NULL +}; + +static DEVICE_ATTR_RO(bandwidth); +static struct attribute *stm32_ddr_pmu_bandwidth_attrs[] = { + &dev_attr_bandwidth.attr, + NULL, +}; + +static struct attribute_group stm32_ddr_pmu_event_attrs_group = { + .name = "events", + .attrs = stm32_ddr_pmu_event_attrs, +}; + +static struct attribute_group stm32_ddr_pmu_bandwidth_attrs_group = { + .attrs = stm32_ddr_pmu_bandwidth_attrs, +}; + +static const struct attribute_group *stm32_ddr_pmu_attr_groups[] = { + &stm32_ddr_pmu_event_attrs_group, + &stm32_ddr_pmu_bandwidth_attrs_group, + NULL, +}; + +static int stm32_ddr_pmu_device_probe(struct platform_device *pdev) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu; + struct reset_control *rst; + struct resource *res; + int i, ret; + u32 val; + + stm32_ddr_pmu = devm_kzalloc(&pdev->dev, sizeof(struct stm32_ddr_pmu), + GFP_KERNEL); + if (!stm32_ddr_pmu) + return -ENOMEM; + platform_set_drvdata(pdev, stm32_ddr_pmu); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + stm32_ddr_pmu->membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(stm32_ddr_pmu->membase)) { + pr_warn("Unable to get STM32 DDR PMU membase\n"); + return PTR_ERR(stm32_ddr_pmu->membase); + } + + stm32_ddr_pmu->clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(stm32_ddr_pmu->clk)) { + pr_warn("Unable to get STM32 DDR PMU clock\n"); + return PTR_ERR(stm32_ddr_pmu->clk); + } + + ret = clk_prepare_enable(stm32_ddr_pmu->clk); + if (ret) { + pr_warn("Unable to prepare STM32 DDR PMU clock\n"); + return ret; + } + + stm32_ddr_pmu->clk_ddr = devm_clk_get(&pdev->dev, "ddr"); + if (IS_ERR(stm32_ddr_pmu->clk_ddr)) { + pr_warn("Unable to get STM32 DDR clock\n"); + return PTR_ERR(stm32_ddr_pmu->clk_ddr); + } + stm32_ddr_pmu->clk_ddr_rate = clk_get_rate(stm32_ddr_pmu->clk_ddr); + stm32_ddr_pmu->clk_ddr_rate /= 1000000; + + stm32_ddr_pmu->poll_period = ms_to_ktime(POLL_MS); + hrtimer_init(&stm32_ddr_pmu->hrtimer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + stm32_ddr_pmu->hrtimer.function = stm32_ddr_pmu_poll; + spin_lock_init(&stm32_ddr_pmu->lock); + + for (i = 0; i < PMU_NR_COUNTERS; i++) { + stm32_ddr_pmu->events[i] = NULL; + stm32_ddr_pmu->events_cnt[i] = 0; + } + + val = readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_SID); + if (val != SID_MAGIC_ID) + return -EINVAL; + + stm32_ddr_pmu->pmu = (struct pmu) { + .task_ctx_nr = perf_invalid_context, + .start = stm32_ddr_pmu_event_start, + .stop = stm32_ddr_pmu_event_stop, + .add = stm32_ddr_pmu_event_add, + .del = stm32_ddr_pmu_event_del, + .event_init = stm32_ddr_pmu_event_init, + .attr_groups = stm32_ddr_pmu_attr_groups, + }; + ret = perf_pmu_register(&stm32_ddr_pmu->pmu, "ddrperfm", -1); + if (ret) { + pr_warn("Unable to register STM32 DDR PMU\n"); + return ret; + } + + rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (!IS_ERR(rst)) { + reset_control_assert(rst); + udelay(2); + reset_control_deassert(rst); + } + + pr_info("stm32-ddr-pmu: probed (ID=0x%08x VER=0x%08x), DDR@%luMHz\n", + readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_ID), + readl_relaxed(stm32_ddr_pmu->membase + DDRPERFM_VER), + stm32_ddr_pmu->clk_ddr_rate); + + clk_disable(stm32_ddr_pmu->clk); + + return 0; +} + +static int stm32_ddr_pmu_device_remove(struct platform_device *pdev) +{ + struct stm32_ddr_pmu *stm32_ddr_pmu = platform_get_drvdata(pdev); + + perf_pmu_unregister(&stm32_ddr_pmu->pmu); + + return 0; +} + +static const struct of_device_id stm32_ddr_pmu_of_match[] = { + { .compatible = "st,stm32-ddr-pmu" }, + { }, +}; + +static struct platform_driver stm32_ddr_pmu_driver = { + .driver = { + .name = "stm32-ddr-pmu", + .of_match_table = of_match_ptr(stm32_ddr_pmu_of_match), + }, + .probe = stm32_ddr_pmu_device_probe, + .remove = stm32_ddr_pmu_device_remove, +}; + +module_platform_driver(stm32_ddr_pmu_driver); + +MODULE_DESCRIPTION("Perf driver for STM32 DDR performance monitor"); +MODULE_AUTHOR("Gerald Baeza "); +MODULE_LICENSE("GPL v2"); -- 2.7.4 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel