aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-04-05 21:29:35 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2018-04-05 21:29:35 -0700
commit38c23685b273cfb4ccf31a199feccce3bdcb5d83 (patch)
tree6b693a36c6ea6c64aaaf34112c57e89f1b5c4b0f /drivers
parentMerge tag 'armsoc-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc (diff)
parentMerge tag 'v4.17-rockchip-drivers-1' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/mmind/linux-rockchip into next/drivers (diff)
downloadlinux-dev-38c23685b273cfb4ccf31a199feccce3bdcb5d83.tar.xz
linux-dev-38c23685b273cfb4ccf31a199feccce3bdcb5d83.zip
Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC driver updates from Arnd Bergmann: "The main addition this time around is the new ARM "SCMI" framework, which is the latest in a series of standards coming from ARM to do power management in a platform independent way. This has been through many review cycles, and it relies on a rather interesting way of using the mailbox subsystem, but in the end I agreed that Sudeep's version was the best we could do after all. Other changes include: - the ARM CCN driver is moved out of drivers/bus into drivers/perf, which makes more sense. Similarly, the performance monitoring portion of the CCI driver are moved the same way and cleaned up a little more. - a series of updates to the SCPI framework - support for the Mediatek mt7623a SoC in drivers/soc - support for additional NVIDIA Tegra hardware in drivers/soc - a new reset driver for Socionext Uniphier - lesser bug fixes in drivers/soc, drivers/tee, drivers/memory, and drivers/firmware and drivers/reset across platforms" * tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (87 commits) reset: uniphier: add ethernet reset control support for PXs3 reset: stm32mp1: Enable stm32mp1 reset driver dt-bindings: reset: add STM32MP1 resets reset: uniphier: add Pro4/Pro5/PXs2 audio systems reset control reset: imx7: add 'depends on HAS_IOMEM' to fix unmet dependency reset: modify the way reset lookup works for board files reset: add support for non-DT systems clk: scmi: use devm_of_clk_add_hw_provider() API and drop scmi_clocks_remove firmware: arm_scmi: prevent accessing rate_discrete uninitialized hwmon: (scmi) return -EINVAL when sensor information is unavailable amlogic: meson-gx-socinfo: Update soc ids soc/tegra: pmc: Use the new reset APIs to manage reset controllers soc: mediatek: update power domain data of MT2712 dt-bindings: soc: update MT2712 power dt-bindings cpufreq: scmi: add thermal dependency soc: mediatek: fix the mistaken pointer accessed when subdomains are added soc: mediatek: add SCPSYS power domain driver for MediaTek MT7623A SoC soc: mediatek: avoid hardcoded value with bus_prot_mask dt-bindings: soc: add header files required for MT7623A SCPSYS dt-binding dt-bindings: soc: add SCPSYS binding for MT7623 and MT7623A SoC ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/bus/Kconfig36
-rw-r--r--drivers/bus/Makefile2
-rw-r--r--drivers/bus/arm-cci.c1763
-rw-r--r--drivers/clk/Kconfig10
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk-scmi.c194
-rw-r--r--drivers/cpufreq/Kconfig.arm12
-rw-r--r--drivers/cpufreq/Makefile1
-rw-r--r--drivers/cpufreq/scmi-cpufreq.c264
-rw-r--r--drivers/firmware/Kconfig34
-rw-r--r--drivers/firmware/Makefile1
-rw-r--r--drivers/firmware/arm_scmi/Makefile5
-rw-r--r--drivers/firmware/arm_scmi/base.c253
-rw-r--r--drivers/firmware/arm_scmi/bus.c221
-rw-r--r--drivers/firmware/arm_scmi/clock.c343
-rw-r--r--drivers/firmware/arm_scmi/common.h105
-rw-r--r--drivers/firmware/arm_scmi/driver.c871
-rw-r--r--drivers/firmware/arm_scmi/perf.c481
-rw-r--r--drivers/firmware/arm_scmi/power.c221
-rw-r--r--drivers/firmware/arm_scmi/scmi_pm_domain.c129
-rw-r--r--drivers/firmware/arm_scmi/sensors.c291
-rw-r--r--drivers/firmware/arm_scpi.c211
-rw-r--r--drivers/firmware/meson/meson_sm.c25
-rw-r--r--drivers/firmware/tegra/bpmp.c144
-rw-r--r--drivers/hwmon/Kconfig12
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/scmi-hwmon.c225
-rw-r--r--drivers/memory/emif.c2
-rw-r--r--drivers/memory/samsung/Kconfig1
-rw-r--r--drivers/memory/samsung/Makefile1
-rw-r--r--drivers/memory/samsung/exynos-srom.c18
-rw-r--r--drivers/memory/samsung/exynos-srom.h7
-rw-r--r--drivers/memory/ti-emif-pm.c1
-rw-r--r--drivers/perf/Kconfig33
-rw-r--r--drivers/perf/Makefile2
-rw-r--r--drivers/perf/arm-cci.c1722
-rw-r--r--drivers/perf/arm-ccn.c (renamed from drivers/bus/arm-ccn.c)0
-rw-r--r--drivers/reset/Kconfig17
-rw-r--r--drivers/reset/Makefile1
-rw-r--r--drivers/reset/core.c96
-rw-r--r--drivers/reset/reset-meson.c22
-rw-r--r--drivers/reset/reset-simple.c2
-rw-r--r--drivers/reset/reset-stm32mp1.c115
-rw-r--r--drivers/reset/reset-uniphier.c5
-rw-r--r--drivers/soc/amlogic/meson-gx-pwrc-vpu.c9
-rw-r--r--drivers/soc/amlogic/meson-gx-socinfo.c11
-rw-r--r--drivers/soc/amlogic/meson-mx-socinfo.c2
-rw-r--r--drivers/soc/imx/gpc.c1
-rw-r--r--drivers/soc/mediatek/mtk-scpsys.c104
-rw-r--r--drivers/soc/qcom/Kconfig1
-rw-r--r--drivers/soc/qcom/rmtfs_mem.c34
-rw-r--r--drivers/soc/qcom/wcnss_ctrl.c2
-rw-r--r--drivers/soc/rockchip/grf.c28
-rw-r--r--drivers/soc/rockchip/pm_domains.c95
-rw-r--r--drivers/soc/samsung/exynos-pmu.c7
-rw-r--r--drivers/soc/tegra/Kconfig10
-rw-r--r--drivers/soc/tegra/pmc.c98
-rw-r--r--drivers/tee/optee/core.c23
-rw-r--r--drivers/tee/optee/optee_smc.h10
-rw-r--r--drivers/tee/tee_core.c14
60 files changed, 6176 insertions, 2174 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
index 769599bc1bab..ff70850031c5 100644
--- a/drivers/bus/Kconfig
+++ b/drivers/bus/Kconfig
@@ -8,25 +8,10 @@ menu "Bus devices"
config ARM_CCI
bool
-config ARM_CCI_PMU
- bool
- select ARM_CCI
-
config ARM_CCI400_COMMON
bool
select ARM_CCI
-config ARM_CCI400_PMU
- bool "ARM CCI400 PMU support"
- depends on (ARM && CPU_V7) || ARM64
- depends on PERF_EVENTS
- select ARM_CCI400_COMMON
- select ARM_CCI_PMU
- help
- Support for PMU events monitoring on the ARM CCI-400 (cache coherent
- interconnect). CCI-400 supports counting events related to the
- connected slave/master interfaces.
-
config ARM_CCI400_PORT_CTRL
bool
depends on ARM && OF && CPU_V7
@@ -35,27 +20,6 @@ config ARM_CCI400_PORT_CTRL
Low level power management driver for CCI400 cache coherent
interconnect for ARM platforms.
-config ARM_CCI5xx_PMU
- bool "ARM CCI-500/CCI-550 PMU support"
- depends on (ARM && CPU_V7) || ARM64
- depends on PERF_EVENTS
- select ARM_CCI_PMU
- help
- Support for PMU events monitoring on the ARM CCI-500/CCI-550 cache
- coherent interconnects. Both of them provide 8 independent event counters,
- which can count events pertaining to the slave/master interfaces as well
- as the internal events to the CCI.
-
- If unsure, say Y
-
-config ARM_CCN
- tristate "ARM CCN driver support"
- depends on ARM || ARM64
- depends on PERF_EVENTS
- help
- PMU (perf) driver supporting the ARM CCN (Cache Coherent Network)
- interconnect.
-
config BRCMSTB_GISB_ARB
bool "Broadcom STB GISB bus arbiter"
depends on ARM || ARM64 || MIPS
diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile
index b666c49f249e..3d473b8adeac 100644
--- a/drivers/bus/Makefile
+++ b/drivers/bus/Makefile
@@ -5,8 +5,6 @@
# Interconnect bus drivers for ARM platforms
obj-$(CONFIG_ARM_CCI) += arm-cci.o
-obj-$(CONFIG_ARM_CCN) += arm-ccn.o
-
obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o
# DPAA2 fsl-mc bus
diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c
index c4c0c8560cce..443e4c3fd357 100644
--- a/drivers/bus/arm-cci.c
+++ b/drivers/bus/arm-cci.c
@@ -16,21 +16,17 @@
#include <linux/arm-cci.h>
#include <linux/io.h>
-#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of_address.h>
-#include <linux/of_irq.h>
#include <linux/of_platform.h>
-#include <linux/perf_event.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include <linux/spinlock.h>
#include <asm/cacheflush.h>
#include <asm/smp_plat.h>
-static void __iomem *cci_ctrl_base;
-static unsigned long cci_ctrl_phys;
+static void __iomem *cci_ctrl_base __ro_after_init;
+static unsigned long cci_ctrl_phys __ro_after_init;
#ifdef CONFIG_ARM_CCI400_PORT_CTRL
struct cci_nb_ports {
@@ -59,1733 +55,26 @@ static const struct of_device_id arm_cci_matches[] = {
{},
};
-#ifdef CONFIG_ARM_CCI_PMU
-
-#define DRIVER_NAME "ARM-CCI"
-#define DRIVER_NAME_PMU DRIVER_NAME " PMU"
-
-#define CCI_PMCR 0x0100
-#define CCI_PID2 0x0fe8
-
-#define CCI_PMCR_CEN 0x00000001
-#define CCI_PMCR_NCNT_MASK 0x0000f800
-#define CCI_PMCR_NCNT_SHIFT 11
-
-#define CCI_PID2_REV_MASK 0xf0
-#define CCI_PID2_REV_SHIFT 4
-
-#define CCI_PMU_EVT_SEL 0x000
-#define CCI_PMU_CNTR 0x004
-#define CCI_PMU_CNTR_CTRL 0x008
-#define CCI_PMU_OVRFLW 0x00c
-
-#define CCI_PMU_OVRFLW_FLAG 1
-
-#define CCI_PMU_CNTR_SIZE(model) ((model)->cntr_size)
-#define CCI_PMU_CNTR_BASE(model, idx) ((idx) * CCI_PMU_CNTR_SIZE(model))
-#define CCI_PMU_CNTR_MASK ((1ULL << 32) -1)
-#define CCI_PMU_CNTR_LAST(cci_pmu) (cci_pmu->num_cntrs - 1)
-
-#define CCI_PMU_MAX_HW_CNTRS(model) \
- ((model)->num_hw_cntrs + (model)->fixed_hw_cntrs)
-
-/* Types of interfaces that can generate events */
-enum {
- CCI_IF_SLAVE,
- CCI_IF_MASTER,
-#ifdef CONFIG_ARM_CCI5xx_PMU
- CCI_IF_GLOBAL,
-#endif
- CCI_IF_MAX,
-};
-
-struct event_range {
- u32 min;
- u32 max;
-};
-
-struct cci_pmu_hw_events {
- struct perf_event **events;
- unsigned long *used_mask;
- raw_spinlock_t pmu_lock;
-};
-
-struct cci_pmu;
-/*
- * struct cci_pmu_model:
- * @fixed_hw_cntrs - Number of fixed event counters
- * @num_hw_cntrs - Maximum number of programmable event counters
- * @cntr_size - Size of an event counter mapping
- */
-struct cci_pmu_model {
- char *name;
- u32 fixed_hw_cntrs;
- u32 num_hw_cntrs;
- u32 cntr_size;
- struct attribute **format_attrs;
- struct attribute **event_attrs;
- struct event_range event_ranges[CCI_IF_MAX];
- int (*validate_hw_event)(struct cci_pmu *, unsigned long);
- int (*get_event_idx)(struct cci_pmu *, struct cci_pmu_hw_events *, unsigned long);
- void (*write_counters)(struct cci_pmu *, unsigned long *);
-};
-
-static struct cci_pmu_model cci_pmu_models[];
-
-struct cci_pmu {
- void __iomem *base;
- struct pmu pmu;
- int nr_irqs;
- int *irqs;
- unsigned long active_irqs;
- const struct cci_pmu_model *model;
- struct cci_pmu_hw_events hw_events;
- struct platform_device *plat_device;
- int num_cntrs;
- atomic_t active_events;
- struct mutex reserve_mutex;
- struct hlist_node node;
- cpumask_t cpus;
-};
-
-#define to_cci_pmu(c) (container_of(c, struct cci_pmu, pmu))
-
-enum cci_models {
-#ifdef CONFIG_ARM_CCI400_PMU
- CCI400_R0,
- CCI400_R1,
-#endif
-#ifdef CONFIG_ARM_CCI5xx_PMU
- CCI500_R0,
- CCI550_R0,
-#endif
- CCI_MODEL_MAX
-};
-
-static void pmu_write_counters(struct cci_pmu *cci_pmu,
- unsigned long *mask);
-static ssize_t cci_pmu_format_show(struct device *dev,
- struct device_attribute *attr, char *buf);
-static ssize_t cci_pmu_event_show(struct device *dev,
- struct device_attribute *attr, char *buf);
-
-#define CCI_EXT_ATTR_ENTRY(_name, _func, _config) \
- &((struct dev_ext_attribute[]) { \
- { __ATTR(_name, S_IRUGO, _func, NULL), (void *)_config } \
- })[0].attr.attr
-
-#define CCI_FORMAT_EXT_ATTR_ENTRY(_name, _config) \
- CCI_EXT_ATTR_ENTRY(_name, cci_pmu_format_show, (char *)_config)
-#define CCI_EVENT_EXT_ATTR_ENTRY(_name, _config) \
- CCI_EXT_ATTR_ENTRY(_name, cci_pmu_event_show, (unsigned long)_config)
-
-/* CCI400 PMU Specific definitions */
-
-#ifdef CONFIG_ARM_CCI400_PMU
-
-/* Port ids */
-#define CCI400_PORT_S0 0
-#define CCI400_PORT_S1 1
-#define CCI400_PORT_S2 2
-#define CCI400_PORT_S3 3
-#define CCI400_PORT_S4 4
-#define CCI400_PORT_M0 5
-#define CCI400_PORT_M1 6
-#define CCI400_PORT_M2 7
-
-#define CCI400_R1_PX 5
-
-/*
- * Instead of an event id to monitor CCI cycles, a dedicated counter is
- * provided. Use 0xff to represent CCI cycles and hope that no future revisions
- * make use of this event in hardware.
- */
-enum cci400_perf_events {
- CCI400_PMU_CYCLES = 0xff
-};
-
-#define CCI400_PMU_CYCLE_CNTR_IDX 0
-#define CCI400_PMU_CNTR0_IDX 1
-
-/*
- * CCI PMU event id is an 8-bit value made of two parts - bits 7:5 for one of 8
- * ports and bits 4:0 are event codes. There are different event codes
- * associated with each port type.
- *
- * Additionally, the range of events associated with the port types changed
- * between Rev0 and Rev1.
- *
- * The constants below define the range of valid codes for each port type for
- * the different revisions and are used to validate the event to be monitored.
- */
-
-#define CCI400_PMU_EVENT_MASK 0xffUL
-#define CCI400_PMU_EVENT_SOURCE_SHIFT 5
-#define CCI400_PMU_EVENT_SOURCE_MASK 0x7
-#define CCI400_PMU_EVENT_CODE_SHIFT 0
-#define CCI400_PMU_EVENT_CODE_MASK 0x1f
-#define CCI400_PMU_EVENT_SOURCE(event) \
- ((event >> CCI400_PMU_EVENT_SOURCE_SHIFT) & \
- CCI400_PMU_EVENT_SOURCE_MASK)
-#define CCI400_PMU_EVENT_CODE(event) \
- ((event >> CCI400_PMU_EVENT_CODE_SHIFT) & CCI400_PMU_EVENT_CODE_MASK)
-
-#define CCI400_R0_SLAVE_PORT_MIN_EV 0x00
-#define CCI400_R0_SLAVE_PORT_MAX_EV 0x13
-#define CCI400_R0_MASTER_PORT_MIN_EV 0x14
-#define CCI400_R0_MASTER_PORT_MAX_EV 0x1a
-
-#define CCI400_R1_SLAVE_PORT_MIN_EV 0x00
-#define CCI400_R1_SLAVE_PORT_MAX_EV 0x14
-#define CCI400_R1_MASTER_PORT_MIN_EV 0x00
-#define CCI400_R1_MASTER_PORT_MAX_EV 0x11
-
-#define CCI400_CYCLE_EVENT_EXT_ATTR_ENTRY(_name, _config) \
- CCI_EXT_ATTR_ENTRY(_name, cci400_pmu_cycle_event_show, \
- (unsigned long)_config)
-
-static ssize_t cci400_pmu_cycle_event_show(struct device *dev,
- struct device_attribute *attr, char *buf);
-
-static struct attribute *cci400_pmu_format_attrs[] = {
- CCI_FORMAT_EXT_ATTR_ENTRY(event, "config:0-4"),
- CCI_FORMAT_EXT_ATTR_ENTRY(source, "config:5-7"),
- NULL
-};
-
-static struct attribute *cci400_r0_pmu_event_attrs[] = {
- /* Slave events */
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_any, 0x0),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_device, 0x01),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_normal_or_nonshareable, 0x2),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_inner_or_outershareable, 0x3),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_cache_maintenance, 0x4),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_mem_barrier, 0x5),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_sync_barrier, 0x6),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg, 0x7),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg_sync, 0x8),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_stall_tt_full, 0x9),
- CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_last_hs_snoop, 0xA),
- CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_stall_rvalids_h_rready_l, 0xB),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_any, 0xC),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_device, 0xD),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_normal_or_nonshareable, 0xE),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_inner_or_outershare_wback_wclean, 0xF),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_write_unique, 0x10),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_write_line_unique, 0x11),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_evict, 0x12),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_stall_tt_full, 0x13),
- /* Master events */
- CCI_EVENT_EXT_ATTR_ENTRY(mi_retry_speculative_fetch, 0x14),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_addr_hazard, 0x15),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_id_hazard, 0x16),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_tt_full, 0x17),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_barrier_hazard, 0x18),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_barrier_hazard, 0x19),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_tt_full, 0x1A),
- /* Special event for cycles counter */
- CCI400_CYCLE_EVENT_EXT_ATTR_ENTRY(cycles, 0xff),
- NULL
-};
-
-static struct attribute *cci400_r1_pmu_event_attrs[] = {
- /* Slave events */
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_any, 0x0),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_device, 0x01),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_normal_or_nonshareable, 0x2),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_inner_or_outershareable, 0x3),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_cache_maintenance, 0x4),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_mem_barrier, 0x5),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_sync_barrier, 0x6),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg, 0x7),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg_sync, 0x8),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_stall_tt_full, 0x9),
- CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_last_hs_snoop, 0xA),
- CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_stall_rvalids_h_rready_l, 0xB),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_any, 0xC),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_device, 0xD),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_normal_or_nonshareable, 0xE),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_inner_or_outershare_wback_wclean, 0xF),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_write_unique, 0x10),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_write_line_unique, 0x11),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_evict, 0x12),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_stall_tt_full, 0x13),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_stall_slave_id_hazard, 0x14),
- /* Master events */
- CCI_EVENT_EXT_ATTR_ENTRY(mi_retry_speculative_fetch, 0x0),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_stall_cycle_addr_hazard, 0x1),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_master_id_hazard, 0x2),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_hi_prio_rtq_full, 0x3),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_barrier_hazard, 0x4),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_barrier_hazard, 0x5),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_wtq_full, 0x6),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_low_prio_rtq_full, 0x7),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_mid_prio_rtq_full, 0x8),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_qvn_vn0, 0x9),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_qvn_vn1, 0xA),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_qvn_vn2, 0xB),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_qvn_vn3, 0xC),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_qvn_vn0, 0xD),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_qvn_vn1, 0xE),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_qvn_vn2, 0xF),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_qvn_vn3, 0x10),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_unique_or_line_unique_addr_hazard, 0x11),
- /* Special event for cycles counter */
- CCI400_CYCLE_EVENT_EXT_ATTR_ENTRY(cycles, 0xff),
- NULL
-};
-
-static ssize_t cci400_pmu_cycle_event_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct dev_ext_attribute *eattr = container_of(attr,
- struct dev_ext_attribute, attr);
- return snprintf(buf, PAGE_SIZE, "config=0x%lx\n", (unsigned long)eattr->var);
-}
-
-static int cci400_get_event_idx(struct cci_pmu *cci_pmu,
- struct cci_pmu_hw_events *hw,
- unsigned long cci_event)
-{
- int idx;
-
- /* cycles event idx is fixed */
- if (cci_event == CCI400_PMU_CYCLES) {
- if (test_and_set_bit(CCI400_PMU_CYCLE_CNTR_IDX, hw->used_mask))
- return -EAGAIN;
-
- return CCI400_PMU_CYCLE_CNTR_IDX;
- }
-
- for (idx = CCI400_PMU_CNTR0_IDX; idx <= CCI_PMU_CNTR_LAST(cci_pmu); ++idx)
- if (!test_and_set_bit(idx, hw->used_mask))
- return idx;
-
- /* No counters available */
- return -EAGAIN;
-}
-
-static int cci400_validate_hw_event(struct cci_pmu *cci_pmu, unsigned long hw_event)
-{
- u8 ev_source = CCI400_PMU_EVENT_SOURCE(hw_event);
- u8 ev_code = CCI400_PMU_EVENT_CODE(hw_event);
- int if_type;
-
- if (hw_event & ~CCI400_PMU_EVENT_MASK)
- return -ENOENT;
-
- if (hw_event == CCI400_PMU_CYCLES)
- return hw_event;
-
- switch (ev_source) {
- case CCI400_PORT_S0:
- case CCI400_PORT_S1:
- case CCI400_PORT_S2:
- case CCI400_PORT_S3:
- case CCI400_PORT_S4:
- /* Slave Interface */
- if_type = CCI_IF_SLAVE;
- break;
- case CCI400_PORT_M0:
- case CCI400_PORT_M1:
- case CCI400_PORT_M2:
- /* Master Interface */
- if_type = CCI_IF_MASTER;
- break;
- default:
- return -ENOENT;
- }
-
- if (ev_code >= cci_pmu->model->event_ranges[if_type].min &&
- ev_code <= cci_pmu->model->event_ranges[if_type].max)
- return hw_event;
-
- return -ENOENT;
-}
-
-static int probe_cci400_revision(void)
-{
- int rev;
- rev = readl_relaxed(cci_ctrl_base + CCI_PID2) & CCI_PID2_REV_MASK;
- rev >>= CCI_PID2_REV_SHIFT;
-
- if (rev < CCI400_R1_PX)
- return CCI400_R0;
- else
- return CCI400_R1;
-}
-
-static const struct cci_pmu_model *probe_cci_model(struct platform_device *pdev)
-{
- if (platform_has_secure_cci_access())
- return &cci_pmu_models[probe_cci400_revision()];
- return NULL;
-}
-#else /* !CONFIG_ARM_CCI400_PMU */
-static inline struct cci_pmu_model *probe_cci_model(struct platform_device *pdev)
-{
- return NULL;
-}
-#endif /* CONFIG_ARM_CCI400_PMU */
-
-#ifdef CONFIG_ARM_CCI5xx_PMU
-
-/*
- * CCI5xx PMU event id is an 9-bit value made of two parts.
- * bits [8:5] - Source for the event
- * bits [4:0] - Event code (specific to type of interface)
- *
- *
- */
-
-/* Port ids */
-#define CCI5xx_PORT_S0 0x0
-#define CCI5xx_PORT_S1 0x1
-#define CCI5xx_PORT_S2 0x2
-#define CCI5xx_PORT_S3 0x3
-#define CCI5xx_PORT_S4 0x4
-#define CCI5xx_PORT_S5 0x5
-#define CCI5xx_PORT_S6 0x6
-
-#define CCI5xx_PORT_M0 0x8
-#define CCI5xx_PORT_M1 0x9
-#define CCI5xx_PORT_M2 0xa
-#define CCI5xx_PORT_M3 0xb
-#define CCI5xx_PORT_M4 0xc
-#define CCI5xx_PORT_M5 0xd
-#define CCI5xx_PORT_M6 0xe
-
-#define CCI5xx_PORT_GLOBAL 0xf
-
-#define CCI5xx_PMU_EVENT_MASK 0x1ffUL
-#define CCI5xx_PMU_EVENT_SOURCE_SHIFT 0x5
-#define CCI5xx_PMU_EVENT_SOURCE_MASK 0xf
-#define CCI5xx_PMU_EVENT_CODE_SHIFT 0x0
-#define CCI5xx_PMU_EVENT_CODE_MASK 0x1f
-
-#define CCI5xx_PMU_EVENT_SOURCE(event) \
- ((event >> CCI5xx_PMU_EVENT_SOURCE_SHIFT) & CCI5xx_PMU_EVENT_SOURCE_MASK)
-#define CCI5xx_PMU_EVENT_CODE(event) \
- ((event >> CCI5xx_PMU_EVENT_CODE_SHIFT) & CCI5xx_PMU_EVENT_CODE_MASK)
-
-#define CCI5xx_SLAVE_PORT_MIN_EV 0x00
-#define CCI5xx_SLAVE_PORT_MAX_EV 0x1f
-#define CCI5xx_MASTER_PORT_MIN_EV 0x00
-#define CCI5xx_MASTER_PORT_MAX_EV 0x06
-#define CCI5xx_GLOBAL_PORT_MIN_EV 0x00
-#define CCI5xx_GLOBAL_PORT_MAX_EV 0x0f
-
-
-#define CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(_name, _config) \
- CCI_EXT_ATTR_ENTRY(_name, cci5xx_pmu_global_event_show, \
- (unsigned long) _config)
-
-static ssize_t cci5xx_pmu_global_event_show(struct device *dev,
- struct device_attribute *attr, char *buf);
-
-static struct attribute *cci5xx_pmu_format_attrs[] = {
- CCI_FORMAT_EXT_ATTR_ENTRY(event, "config:0-4"),
- CCI_FORMAT_EXT_ATTR_ENTRY(source, "config:5-8"),
- NULL,
-};
-
-static struct attribute *cci5xx_pmu_event_attrs[] = {
- /* Slave events */
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_arvalid, 0x0),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_dev, 0x1),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_nonshareable, 0x2),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_shareable_non_alloc, 0x3),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_shareable_alloc, 0x4),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_invalidate, 0x5),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_cache_maint, 0x6),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg, 0x7),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_rval, 0x8),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_rlast_snoop, 0x9),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_awalid, 0xA),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_dev, 0xB),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_non_shareable, 0xC),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_share_wb, 0xD),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_share_wlu, 0xE),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_share_wunique, 0xF),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_evict, 0x10),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_wrevict, 0x11),
- CCI_EVENT_EXT_ATTR_ENTRY(si_w_data_beat, 0x12),
- CCI_EVENT_EXT_ATTR_ENTRY(si_srq_acvalid, 0x13),
- CCI_EVENT_EXT_ATTR_ENTRY(si_srq_read, 0x14),
- CCI_EVENT_EXT_ATTR_ENTRY(si_srq_clean, 0x15),
- CCI_EVENT_EXT_ATTR_ENTRY(si_srq_data_transfer_low, 0x16),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_stall_arvalid, 0x17),
- CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_stall, 0x18),
- CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_stall, 0x19),
- CCI_EVENT_EXT_ATTR_ENTRY(si_w_data_stall, 0x1A),
- CCI_EVENT_EXT_ATTR_ENTRY(si_w_resp_stall, 0x1B),
- CCI_EVENT_EXT_ATTR_ENTRY(si_srq_stall, 0x1C),
- CCI_EVENT_EXT_ATTR_ENTRY(si_s_data_stall, 0x1D),
- CCI_EVENT_EXT_ATTR_ENTRY(si_rq_stall_ot_limit, 0x1E),
- CCI_EVENT_EXT_ATTR_ENTRY(si_r_stall_arbit, 0x1F),
-
- /* Master events */
- CCI_EVENT_EXT_ATTR_ENTRY(mi_r_data_beat_any, 0x0),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_w_data_beat_any, 0x1),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall, 0x2),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_r_data_stall, 0x3),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall, 0x4),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_w_data_stall, 0x5),
- CCI_EVENT_EXT_ATTR_ENTRY(mi_w_resp_stall, 0x6),
-
- /* Global events */
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_filter_bank_0_1, 0x0),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_filter_bank_2_3, 0x1),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_filter_bank_4_5, 0x2),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_filter_bank_6_7, 0x3),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_miss_filter_bank_0_1, 0x4),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_miss_filter_bank_2_3, 0x5),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_miss_filter_bank_4_5, 0x6),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_miss_filter_bank_6_7, 0x7),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_back_invalidation, 0x8),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_stall_alloc_busy, 0x9),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_stall_tt_full, 0xA),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_wrq, 0xB),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_cd_hs, 0xC),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_rq_stall_addr_hazard, 0xD),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_rq_stall_tt_full, 0xE),
- CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_rq_tzmp1_prot, 0xF),
- NULL
-};
-
-static ssize_t cci5xx_pmu_global_event_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct dev_ext_attribute *eattr = container_of(attr,
- struct dev_ext_attribute, attr);
- /* Global events have single fixed source code */
- return snprintf(buf, PAGE_SIZE, "event=0x%lx,source=0x%x\n",
- (unsigned long)eattr->var, CCI5xx_PORT_GLOBAL);
-}
-
-/*
- * CCI500 provides 8 independent event counters that can count
- * any of the events available.
- * CCI500 PMU event source ids
- * 0x0-0x6 - Slave interfaces
- * 0x8-0xD - Master interfaces
- * 0xf - Global Events
- * 0x7,0xe - Reserved
- */
-static int cci500_validate_hw_event(struct cci_pmu *cci_pmu,
- unsigned long hw_event)
-{
- u32 ev_source = CCI5xx_PMU_EVENT_SOURCE(hw_event);
- u32 ev_code = CCI5xx_PMU_EVENT_CODE(hw_event);
- int if_type;
-
- if (hw_event & ~CCI5xx_PMU_EVENT_MASK)
- return -ENOENT;
-
- switch (ev_source) {
- case CCI5xx_PORT_S0:
- case CCI5xx_PORT_S1:
- case CCI5xx_PORT_S2:
- case CCI5xx_PORT_S3:
- case CCI5xx_PORT_S4:
- case CCI5xx_PORT_S5:
- case CCI5xx_PORT_S6:
- if_type = CCI_IF_SLAVE;
- break;
- case CCI5xx_PORT_M0:
- case CCI5xx_PORT_M1:
- case CCI5xx_PORT_M2:
- case CCI5xx_PORT_M3:
- case CCI5xx_PORT_M4:
- case CCI5xx_PORT_M5:
- if_type = CCI_IF_MASTER;
- break;
- case CCI5xx_PORT_GLOBAL:
- if_type = CCI_IF_GLOBAL;
- break;
- default:
- return -ENOENT;
- }
-
- if (ev_code >= cci_pmu->model->event_ranges[if_type].min &&
- ev_code <= cci_pmu->model->event_ranges[if_type].max)
- return hw_event;
-
- return -ENOENT;
-}
-
-/*
- * CCI550 provides 8 independent event counters that can count
- * any of the events available.
- * CCI550 PMU event source ids
- * 0x0-0x6 - Slave interfaces
- * 0x8-0xe - Master interfaces
- * 0xf - Global Events
- * 0x7 - Reserved
- */
-static int cci550_validate_hw_event(struct cci_pmu *cci_pmu,
- unsigned long hw_event)
-{
- u32 ev_source = CCI5xx_PMU_EVENT_SOURCE(hw_event);
- u32 ev_code = CCI5xx_PMU_EVENT_CODE(hw_event);
- int if_type;
-
- if (hw_event & ~CCI5xx_PMU_EVENT_MASK)
- return -ENOENT;
-
- switch (ev_source) {
- case CCI5xx_PORT_S0:
- case CCI5xx_PORT_S1:
- case CCI5xx_PORT_S2:
- case CCI5xx_PORT_S3:
- case CCI5xx_PORT_S4:
- case CCI5xx_PORT_S5:
- case CCI5xx_PORT_S6:
- if_type = CCI_IF_SLAVE;
- break;
- case CCI5xx_PORT_M0:
- case CCI5xx_PORT_M1:
- case CCI5xx_PORT_M2:
- case CCI5xx_PORT_M3:
- case CCI5xx_PORT_M4:
- case CCI5xx_PORT_M5:
- case CCI5xx_PORT_M6:
- if_type = CCI_IF_MASTER;
- break;
- case CCI5xx_PORT_GLOBAL:
- if_type = CCI_IF_GLOBAL;
- break;
- default:
- return -ENOENT;
- }
-
- if (ev_code >= cci_pmu->model->event_ranges[if_type].min &&
- ev_code <= cci_pmu->model->event_ranges[if_type].max)
- return hw_event;
-
- return -ENOENT;
-}
-
-#endif /* CONFIG_ARM_CCI5xx_PMU */
-
-/*
- * Program the CCI PMU counters which have PERF_HES_ARCH set
- * with the event period and mark them ready before we enable
- * PMU.
- */
-static void cci_pmu_sync_counters(struct cci_pmu *cci_pmu)
-{
- int i;
- struct cci_pmu_hw_events *cci_hw = &cci_pmu->hw_events;
-
- DECLARE_BITMAP(mask, cci_pmu->num_cntrs);
-
- bitmap_zero(mask, cci_pmu->num_cntrs);
- for_each_set_bit(i, cci_pmu->hw_events.used_mask, cci_pmu->num_cntrs) {
- struct perf_event *event = cci_hw->events[i];
-
- if (WARN_ON(!event))
- continue;
-
- /* Leave the events which are not counting */
- if (event->hw.state & PERF_HES_STOPPED)
- continue;
- if (event->hw.state & PERF_HES_ARCH) {
- set_bit(i, mask);
- event->hw.state &= ~PERF_HES_ARCH;
- }
- }
-
- pmu_write_counters(cci_pmu, mask);
-}
-
-/* Should be called with cci_pmu->hw_events->pmu_lock held */
-static void __cci_pmu_enable_nosync(struct cci_pmu *cci_pmu)
-{
- u32 val;
-
- /* Enable all the PMU counters. */
- val = readl_relaxed(cci_ctrl_base + CCI_PMCR) | CCI_PMCR_CEN;
- writel(val, cci_ctrl_base + CCI_PMCR);
-}
-
-/* Should be called with cci_pmu->hw_events->pmu_lock held */
-static void __cci_pmu_enable_sync(struct cci_pmu *cci_pmu)
-{
- cci_pmu_sync_counters(cci_pmu);
- __cci_pmu_enable_nosync(cci_pmu);
-}
-
-/* Should be called with cci_pmu->hw_events->pmu_lock held */
-static void __cci_pmu_disable(void)
-{
- u32 val;
-
- /* Disable all the PMU counters. */
- val = readl_relaxed(cci_ctrl_base + CCI_PMCR) & ~CCI_PMCR_CEN;
- writel(val, cci_ctrl_base + CCI_PMCR);
-}
-
-static ssize_t cci_pmu_format_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct dev_ext_attribute *eattr = container_of(attr,
- struct dev_ext_attribute, attr);
- return snprintf(buf, PAGE_SIZE, "%s\n", (char *)eattr->var);
-}
-
-static ssize_t cci_pmu_event_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct dev_ext_attribute *eattr = container_of(attr,
- struct dev_ext_attribute, attr);
- /* source parameter is mandatory for normal PMU events */
- return snprintf(buf, PAGE_SIZE, "source=?,event=0x%lx\n",
- (unsigned long)eattr->var);
-}
-
-static int pmu_is_valid_counter(struct cci_pmu *cci_pmu, int idx)
-{
- return 0 <= idx && idx <= CCI_PMU_CNTR_LAST(cci_pmu);
-}
-
-static u32 pmu_read_register(struct cci_pmu *cci_pmu, int idx, unsigned int offset)
-{
- return readl_relaxed(cci_pmu->base +
- CCI_PMU_CNTR_BASE(cci_pmu->model, idx) + offset);
-}
-
-static void pmu_write_register(struct cci_pmu *cci_pmu, u32 value,
- int idx, unsigned int offset)
-{
- writel_relaxed(value, cci_pmu->base +
- CCI_PMU_CNTR_BASE(cci_pmu->model, idx) + offset);
-}
-
-static void pmu_disable_counter(struct cci_pmu *cci_pmu, int idx)
-{
- pmu_write_register(cci_pmu, 0, idx, CCI_PMU_CNTR_CTRL);
-}
-
-static void pmu_enable_counter(struct cci_pmu *cci_pmu, int idx)
-{
- pmu_write_register(cci_pmu, 1, idx, CCI_PMU_CNTR_CTRL);
-}
-
-static bool __maybe_unused
-pmu_counter_is_enabled(struct cci_pmu *cci_pmu, int idx)
-{
- return (pmu_read_register(cci_pmu, idx, CCI_PMU_CNTR_CTRL) & 0x1) != 0;
-}
-
-static void pmu_set_event(struct cci_pmu *cci_pmu, int idx, unsigned long event)
-{
- pmu_write_register(cci_pmu, event, idx, CCI_PMU_EVT_SEL);
-}
-
-/*
- * For all counters on the CCI-PMU, disable any 'enabled' counters,
- * saving the changed counters in the mask, so that we can restore
- * it later using pmu_restore_counters. The mask is private to the
- * caller. We cannot rely on the used_mask maintained by the CCI_PMU
- * as it only tells us if the counter is assigned to perf_event or not.
- * The state of the perf_event cannot be locked by the PMU layer, hence
- * we check the individual counter status (which can be locked by
- * cci_pm->hw_events->pmu_lock).
- *
- * @mask should be initialised to empty by the caller.
- */
-static void __maybe_unused
-pmu_save_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
-{
- int i;
-
- for (i = 0; i < cci_pmu->num_cntrs; i++) {
- if (pmu_counter_is_enabled(cci_pmu, i)) {
- set_bit(i, mask);
- pmu_disable_counter(cci_pmu, i);
- }
- }
-}
-
-/*
- * Restore the status of the counters. Reversal of the pmu_save_counters().
- * For each counter set in the mask, enable the counter back.
- */
-static void __maybe_unused
-pmu_restore_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
-{
- int i;
-
- for_each_set_bit(i, mask, cci_pmu->num_cntrs)
- pmu_enable_counter(cci_pmu, i);
-}
-
-/*
- * Returns the number of programmable counters actually implemented
- * by the cci
- */
-static u32 pmu_get_max_counters(void)
-{
- return (readl_relaxed(cci_ctrl_base + CCI_PMCR) &
- CCI_PMCR_NCNT_MASK) >> CCI_PMCR_NCNT_SHIFT;
-}
-
-static int pmu_get_event_idx(struct cci_pmu_hw_events *hw, struct perf_event *event)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- unsigned long cci_event = event->hw.config_base;
- int idx;
-
- if (cci_pmu->model->get_event_idx)
- return cci_pmu->model->get_event_idx(cci_pmu, hw, cci_event);
-
- /* Generic code to find an unused idx from the mask */
- for(idx = 0; idx <= CCI_PMU_CNTR_LAST(cci_pmu); idx++)
- if (!test_and_set_bit(idx, hw->used_mask))
- return idx;
-
- /* No counters available */
- return -EAGAIN;
-}
-
-static int pmu_map_event(struct perf_event *event)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
-
- if (event->attr.type < PERF_TYPE_MAX ||
- !cci_pmu->model->validate_hw_event)
- return -ENOENT;
-
- return cci_pmu->model->validate_hw_event(cci_pmu, event->attr.config);
-}
-
-static int pmu_request_irq(struct cci_pmu *cci_pmu, irq_handler_t handler)
-{
- int i;
- struct platform_device *pmu_device = cci_pmu->plat_device;
-
- if (unlikely(!pmu_device))
- return -ENODEV;
-
- if (cci_pmu->nr_irqs < 1) {
- dev_err(&pmu_device->dev, "no irqs for CCI PMUs defined\n");
- return -ENODEV;
- }
-
- /*
- * Register all available CCI PMU interrupts. In the interrupt handler
- * we iterate over the counters checking for interrupt source (the
- * overflowing counter) and clear it.
- *
- * This should allow handling of non-unique interrupt for the counters.
- */
- for (i = 0; i < cci_pmu->nr_irqs; i++) {
- int err = request_irq(cci_pmu->irqs[i], handler, IRQF_SHARED,
- "arm-cci-pmu", cci_pmu);
- if (err) {
- dev_err(&pmu_device->dev, "unable to request IRQ%d for ARM CCI PMU counters\n",
- cci_pmu->irqs[i]);
- return err;
- }
-
- set_bit(i, &cci_pmu->active_irqs);
- }
-
- return 0;
-}
-
-static void pmu_free_irq(struct cci_pmu *cci_pmu)
-{
- int i;
-
- for (i = 0; i < cci_pmu->nr_irqs; i++) {
- if (!test_and_clear_bit(i, &cci_pmu->active_irqs))
- continue;
-
- free_irq(cci_pmu->irqs[i], cci_pmu);
- }
-}
-
-static u32 pmu_read_counter(struct perf_event *event)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- struct hw_perf_event *hw_counter = &event->hw;
- int idx = hw_counter->idx;
- u32 value;
-
- if (unlikely(!pmu_is_valid_counter(cci_pmu, idx))) {
- dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
- return 0;
- }
- value = pmu_read_register(cci_pmu, idx, CCI_PMU_CNTR);
-
- return value;
-}
-
-static void pmu_write_counter(struct cci_pmu *cci_pmu, u32 value, int idx)
-{
- pmu_write_register(cci_pmu, value, idx, CCI_PMU_CNTR);
-}
-
-static void __pmu_write_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
-{
- int i;
- struct cci_pmu_hw_events *cci_hw = &cci_pmu->hw_events;
-
- for_each_set_bit(i, mask, cci_pmu->num_cntrs) {
- struct perf_event *event = cci_hw->events[i];
-
- if (WARN_ON(!event))
- continue;
- pmu_write_counter(cci_pmu, local64_read(&event->hw.prev_count), i);
- }
-}
-
-static void pmu_write_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
-{
- if (cci_pmu->model->write_counters)
- cci_pmu->model->write_counters(cci_pmu, mask);
- else
- __pmu_write_counters(cci_pmu, mask);
-}
-
-#ifdef CONFIG_ARM_CCI5xx_PMU
-
-/*
- * CCI-500/CCI-550 has advanced power saving policies, which could gate the
- * clocks to the PMU counters, which makes the writes to them ineffective.
- * The only way to write to those counters is when the global counters
- * are enabled and the particular counter is enabled.
- *
- * So we do the following :
- *
- * 1) Disable all the PMU counters, saving their current state
- * 2) Enable the global PMU profiling, now that all counters are
- * disabled.
- *
- * For each counter to be programmed, repeat steps 3-7:
- *
- * 3) Write an invalid event code to the event control register for the
- counter, so that the counters are not modified.
- * 4) Enable the counter control for the counter.
- * 5) Set the counter value
- * 6) Disable the counter
- * 7) Restore the event in the target counter
- *
- * 8) Disable the global PMU.
- * 9) Restore the status of the rest of the counters.
- *
- * We choose an event which for CCI-5xx is guaranteed not to count.
- * We use the highest possible event code (0x1f) for the master interface 0.
- */
-#define CCI5xx_INVALID_EVENT ((CCI5xx_PORT_M0 << CCI5xx_PMU_EVENT_SOURCE_SHIFT) | \
- (CCI5xx_PMU_EVENT_CODE_MASK << CCI5xx_PMU_EVENT_CODE_SHIFT))
-static void cci5xx_pmu_write_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
-{
- int i;
- DECLARE_BITMAP(saved_mask, cci_pmu->num_cntrs);
-
- bitmap_zero(saved_mask, cci_pmu->num_cntrs);
- pmu_save_counters(cci_pmu, saved_mask);
-
- /*
- * Now that all the counters are disabled, we can safely turn the PMU on,
- * without syncing the status of the counters
- */
- __cci_pmu_enable_nosync(cci_pmu);
-
- for_each_set_bit(i, mask, cci_pmu->num_cntrs) {
- struct perf_event *event = cci_pmu->hw_events.events[i];
-
- if (WARN_ON(!event))
- continue;
-
- pmu_set_event(cci_pmu, i, CCI5xx_INVALID_EVENT);
- pmu_enable_counter(cci_pmu, i);
- pmu_write_counter(cci_pmu, local64_read(&event->hw.prev_count), i);
- pmu_disable_counter(cci_pmu, i);
- pmu_set_event(cci_pmu, i, event->hw.config_base);
- }
-
- __cci_pmu_disable();
-
- pmu_restore_counters(cci_pmu, saved_mask);
-}
-
-#endif /* CONFIG_ARM_CCI5xx_PMU */
-
-static u64 pmu_event_update(struct perf_event *event)
-{
- struct hw_perf_event *hwc = &event->hw;
- u64 delta, prev_raw_count, new_raw_count;
-
- do {
- prev_raw_count = local64_read(&hwc->prev_count);
- new_raw_count = pmu_read_counter(event);
- } while (local64_cmpxchg(&hwc->prev_count, prev_raw_count,
- new_raw_count) != prev_raw_count);
-
- delta = (new_raw_count - prev_raw_count) & CCI_PMU_CNTR_MASK;
-
- local64_add(delta, &event->count);
-
- return new_raw_count;
-}
-
-static void pmu_read(struct perf_event *event)
-{
- pmu_event_update(event);
-}
-
-static void pmu_event_set_period(struct perf_event *event)
-{
- struct hw_perf_event *hwc = &event->hw;
- /*
- * The CCI PMU counters have a period of 2^32. To account for the
- * possiblity of extreme interrupt latency we program for a period of
- * half that. Hopefully we can handle the interrupt before another 2^31
- * events occur and the counter overtakes its previous value.
- */
- u64 val = 1ULL << 31;
- local64_set(&hwc->prev_count, val);
-
- /*
- * CCI PMU uses PERF_HES_ARCH to keep track of the counters, whose
- * values needs to be sync-ed with the s/w state before the PMU is
- * enabled.
- * Mark this counter for sync.
- */
- hwc->state |= PERF_HES_ARCH;
-}
-
-static irqreturn_t pmu_handle_irq(int irq_num, void *dev)
-{
- unsigned long flags;
- struct cci_pmu *cci_pmu = dev;
- struct cci_pmu_hw_events *events = &cci_pmu->hw_events;
- int idx, handled = IRQ_NONE;
-
- raw_spin_lock_irqsave(&events->pmu_lock, flags);
-
- /* Disable the PMU while we walk through the counters */
- __cci_pmu_disable();
- /*
- * Iterate over counters and update the corresponding perf events.
- * This should work regardless of whether we have per-counter overflow
- * interrupt or a combined overflow interrupt.
- */
- for (idx = 0; idx <= CCI_PMU_CNTR_LAST(cci_pmu); idx++) {
- struct perf_event *event = events->events[idx];
-
- if (!event)
- continue;
-
- /* Did this counter overflow? */
- if (!(pmu_read_register(cci_pmu, idx, CCI_PMU_OVRFLW) &
- CCI_PMU_OVRFLW_FLAG))
- continue;
-
- pmu_write_register(cci_pmu, CCI_PMU_OVRFLW_FLAG, idx,
- CCI_PMU_OVRFLW);
-
- pmu_event_update(event);
- pmu_event_set_period(event);
- handled = IRQ_HANDLED;
- }
-
- /* Enable the PMU and sync possibly overflowed counters */
- __cci_pmu_enable_sync(cci_pmu);
- raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
-
- return IRQ_RETVAL(handled);
-}
-
-static int cci_pmu_get_hw(struct cci_pmu *cci_pmu)
-{
- int ret = pmu_request_irq(cci_pmu, pmu_handle_irq);
- if (ret) {
- pmu_free_irq(cci_pmu);
- return ret;
- }
- return 0;
-}
-
-static void cci_pmu_put_hw(struct cci_pmu *cci_pmu)
-{
- pmu_free_irq(cci_pmu);
-}
-
-static void hw_perf_event_destroy(struct perf_event *event)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- atomic_t *active_events = &cci_pmu->active_events;
- struct mutex *reserve_mutex = &cci_pmu->reserve_mutex;
-
- if (atomic_dec_and_mutex_lock(active_events, reserve_mutex)) {
- cci_pmu_put_hw(cci_pmu);
- mutex_unlock(reserve_mutex);
- }
-}
-
-static void cci_pmu_enable(struct pmu *pmu)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(pmu);
- struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
- int enabled = bitmap_weight(hw_events->used_mask, cci_pmu->num_cntrs);
- unsigned long flags;
-
- if (!enabled)
- return;
-
- raw_spin_lock_irqsave(&hw_events->pmu_lock, flags);
- __cci_pmu_enable_sync(cci_pmu);
- raw_spin_unlock_irqrestore(&hw_events->pmu_lock, flags);
-
-}
-
-static void cci_pmu_disable(struct pmu *pmu)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(pmu);
- struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
- unsigned long flags;
-
- raw_spin_lock_irqsave(&hw_events->pmu_lock, flags);
- __cci_pmu_disable();
- raw_spin_unlock_irqrestore(&hw_events->pmu_lock, flags);
-}
-
-/*
- * Check if the idx represents a non-programmable counter.
- * All the fixed event counters are mapped before the programmable
- * counters.
- */
-static bool pmu_fixed_hw_idx(struct cci_pmu *cci_pmu, int idx)
-{
- return (idx >= 0) && (idx < cci_pmu->model->fixed_hw_cntrs);
-}
-
-static void cci_pmu_start(struct perf_event *event, int pmu_flags)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
- struct hw_perf_event *hwc = &event->hw;
- int idx = hwc->idx;
- unsigned long flags;
-
- /*
- * To handle interrupt latency, we always reprogram the period
- * regardlesss of PERF_EF_RELOAD.
- */
- if (pmu_flags & PERF_EF_RELOAD)
- WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE));
-
- hwc->state = 0;
-
- if (unlikely(!pmu_is_valid_counter(cci_pmu, idx))) {
- dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
- return;
- }
-
- raw_spin_lock_irqsave(&hw_events->pmu_lock, flags);
-
- /* Configure the counter unless you are counting a fixed event */
- if (!pmu_fixed_hw_idx(cci_pmu, idx))
- pmu_set_event(cci_pmu, idx, hwc->config_base);
-
- pmu_event_set_period(event);
- pmu_enable_counter(cci_pmu, idx);
-
- raw_spin_unlock_irqrestore(&hw_events->pmu_lock, flags);
-}
-
-static void cci_pmu_stop(struct perf_event *event, int pmu_flags)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- struct hw_perf_event *hwc = &event->hw;
- int idx = hwc->idx;
-
- if (hwc->state & PERF_HES_STOPPED)
- return;
-
- if (unlikely(!pmu_is_valid_counter(cci_pmu, idx))) {
- dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
- return;
- }
-
- /*
- * We always reprogram the counter, so ignore PERF_EF_UPDATE. See
- * cci_pmu_start()
- */
- pmu_disable_counter(cci_pmu, idx);
- pmu_event_update(event);
- hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
-}
-
-static int cci_pmu_add(struct perf_event *event, int flags)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
- struct hw_perf_event *hwc = &event->hw;
- int idx;
- int err = 0;
-
- perf_pmu_disable(event->pmu);
-
- /* If we don't have a space for the counter then finish early. */
- idx = pmu_get_event_idx(hw_events, event);
- if (idx < 0) {
- err = idx;
- goto out;
- }
-
- event->hw.idx = idx;
- hw_events->events[idx] = event;
-
- hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
- if (flags & PERF_EF_START)
- cci_pmu_start(event, PERF_EF_RELOAD);
-
- /* Propagate our changes to the userspace mapping. */
- perf_event_update_userpage(event);
-
-out:
- perf_pmu_enable(event->pmu);
- return err;
-}
-
-static void cci_pmu_del(struct perf_event *event, int flags)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
- struct hw_perf_event *hwc = &event->hw;
- int idx = hwc->idx;
-
- cci_pmu_stop(event, PERF_EF_UPDATE);
- hw_events->events[idx] = NULL;
- clear_bit(idx, hw_events->used_mask);
-
- perf_event_update_userpage(event);
-}
-
-static int
-validate_event(struct pmu *cci_pmu,
- struct cci_pmu_hw_events *hw_events,
- struct perf_event *event)
-{
- if (is_software_event(event))
- return 1;
-
- /*
- * Reject groups spanning multiple HW PMUs (e.g. CPU + CCI). The
- * core perf code won't check that the pmu->ctx == leader->ctx
- * until after pmu->event_init(event).
- */
- if (event->pmu != cci_pmu)
- return 0;
-
- if (event->state < PERF_EVENT_STATE_OFF)
- return 1;
-
- if (event->state == PERF_EVENT_STATE_OFF && !event->attr.enable_on_exec)
- return 1;
-
- return pmu_get_event_idx(hw_events, event) >= 0;
-}
-
-static int
-validate_group(struct perf_event *event)
-{
- struct perf_event *sibling, *leader = event->group_leader;
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- unsigned long mask[BITS_TO_LONGS(cci_pmu->num_cntrs)];
- struct cci_pmu_hw_events fake_pmu = {
- /*
- * Initialise the fake PMU. We only need to populate the
- * used_mask for the purposes of validation.
- */
- .used_mask = mask,
- };
- memset(mask, 0, BITS_TO_LONGS(cci_pmu->num_cntrs) * sizeof(unsigned long));
-
- if (!validate_event(event->pmu, &fake_pmu, leader))
- return -EINVAL;
-
- for_each_sibling_event(sibling, leader) {
- if (!validate_event(event->pmu, &fake_pmu, sibling))
- return -EINVAL;
- }
-
- if (!validate_event(event->pmu, &fake_pmu, event))
- return -EINVAL;
-
- return 0;
-}
-
-static int
-__hw_perf_event_init(struct perf_event *event)
-{
- struct hw_perf_event *hwc = &event->hw;
- int mapping;
-
- mapping = pmu_map_event(event);
-
- if (mapping < 0) {
- pr_debug("event %x:%llx not supported\n", event->attr.type,
- event->attr.config);
- return mapping;
- }
-
- /*
- * We don't assign an index until we actually place the event onto
- * hardware. Use -1 to signify that we haven't decided where to put it
- * yet.
- */
- hwc->idx = -1;
- hwc->config_base = 0;
- hwc->config = 0;
- hwc->event_base = 0;
-
- /*
- * Store the event encoding into the config_base field.
- */
- hwc->config_base |= (unsigned long)mapping;
-
- /*
- * Limit the sample_period to half of the counter width. That way, the
- * new counter value is far less likely to overtake the previous one
- * unless you have some serious IRQ latency issues.
- */
- hwc->sample_period = CCI_PMU_CNTR_MASK >> 1;
- hwc->last_period = hwc->sample_period;
- local64_set(&hwc->period_left, hwc->sample_period);
-
- if (event->group_leader != event) {
- if (validate_group(event) != 0)
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int cci_pmu_event_init(struct perf_event *event)
-{
- struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
- atomic_t *active_events = &cci_pmu->active_events;
- int err = 0;
- int cpu;
-
- if (event->attr.type != event->pmu->type)
- return -ENOENT;
-
- /* Shared by all CPUs, no meaningful state to sample */
- if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK)
- return -EOPNOTSUPP;
-
- /* We have no filtering of any kind */
- 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;
-
- /*
- * Following the example set by other "uncore" PMUs, we accept any CPU
- * and rewrite its affinity dynamically rather than having perf core
- * handle cpu == -1 and pid == -1 for this case.
- *
- * The perf core will pin online CPUs for the duration of this call and
- * the event being installed into its context, so the PMU's CPU can't
- * change under our feet.
- */
- cpu = cpumask_first(&cci_pmu->cpus);
- if (event->cpu < 0 || cpu < 0)
- return -EINVAL;
- event->cpu = cpu;
-
- event->destroy = hw_perf_event_destroy;
- if (!atomic_inc_not_zero(active_events)) {
- mutex_lock(&cci_pmu->reserve_mutex);
- if (atomic_read(active_events) == 0)
- err = cci_pmu_get_hw(cci_pmu);
- if (!err)
- atomic_inc(active_events);
- mutex_unlock(&cci_pmu->reserve_mutex);
- }
- if (err)
- return err;
-
- err = __hw_perf_event_init(event);
- if (err)
- hw_perf_event_destroy(event);
-
- return err;
-}
-
-static ssize_t pmu_cpumask_attr_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct pmu *pmu = dev_get_drvdata(dev);
- struct cci_pmu *cci_pmu = to_cci_pmu(pmu);
-
- int n = scnprintf(buf, PAGE_SIZE - 1, "%*pbl",
- cpumask_pr_args(&cci_pmu->cpus));
- buf[n++] = '\n';
- buf[n] = '\0';
- return n;
-}
-
-static struct device_attribute pmu_cpumask_attr =
- __ATTR(cpumask, S_IRUGO, pmu_cpumask_attr_show, NULL);
-
-static struct attribute *pmu_attrs[] = {
- &pmu_cpumask_attr.attr,
- NULL,
-};
-
-static struct attribute_group pmu_attr_group = {
- .attrs = pmu_attrs,
-};
-
-static struct attribute_group pmu_format_attr_group = {
- .name = "format",
- .attrs = NULL, /* Filled in cci_pmu_init_attrs */
-};
-
-static struct attribute_group pmu_event_attr_group = {
- .name = "events",
- .attrs = NULL, /* Filled in cci_pmu_init_attrs */
-};
-
-static const struct attribute_group *pmu_attr_groups[] = {
- &pmu_attr_group,
- &pmu_format_attr_group,
- &pmu_event_attr_group,
- NULL
-};
-
-static int cci_pmu_init(struct cci_pmu *cci_pmu, struct platform_device *pdev)
-{
- const struct cci_pmu_model *model = cci_pmu->model;
- char *name = model->name;
- u32 num_cntrs;
-
- pmu_event_attr_group.attrs = model->event_attrs;
- pmu_format_attr_group.attrs = model->format_attrs;
-
- cci_pmu->pmu = (struct pmu) {
- .name = cci_pmu->model->name,
- .task_ctx_nr = perf_invalid_context,
- .pmu_enable = cci_pmu_enable,
- .pmu_disable = cci_pmu_disable,
- .event_init = cci_pmu_event_init,
- .add = cci_pmu_add,
- .del = cci_pmu_del,
- .start = cci_pmu_start,
- .stop = cci_pmu_stop,
- .read = pmu_read,
- .attr_groups = pmu_attr_groups,
- };
-
- cci_pmu->plat_device = pdev;
- num_cntrs = pmu_get_max_counters();
- if (num_cntrs > cci_pmu->model->num_hw_cntrs) {
- dev_warn(&pdev->dev,
- "PMU implements more counters(%d) than supported by"
- " the model(%d), truncated.",
- num_cntrs, cci_pmu->model->num_hw_cntrs);
- num_cntrs = cci_pmu->model->num_hw_cntrs;
- }
- cci_pmu->num_cntrs = num_cntrs + cci_pmu->model->fixed_hw_cntrs;
-
- return perf_pmu_register(&cci_pmu->pmu, name, -1);
-}
-
-static int cci_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node)
-{
- struct cci_pmu *cci_pmu = hlist_entry_safe(node, struct cci_pmu, node);
- unsigned int target;
-
- if (!cpumask_test_and_clear_cpu(cpu, &cci_pmu->cpus))
- return 0;
- target = cpumask_any_but(cpu_online_mask, cpu);
- if (target >= nr_cpu_ids)
- return 0;
- /*
- * TODO: migrate context once core races on event->ctx have
- * been fixed.
- */
- cpumask_set_cpu(target, &cci_pmu->cpus);
- return 0;
-}
-
-static struct cci_pmu_model cci_pmu_models[] = {
-#ifdef CONFIG_ARM_CCI400_PMU
- [CCI400_R0] = {
- .name = "CCI_400",
- .fixed_hw_cntrs = 1, /* Cycle counter */
- .num_hw_cntrs = 4,
- .cntr_size = SZ_4K,
- .format_attrs = cci400_pmu_format_attrs,
- .event_attrs = cci400_r0_pmu_event_attrs,
- .event_ranges = {
- [CCI_IF_SLAVE] = {
- CCI400_R0_SLAVE_PORT_MIN_EV,
- CCI400_R0_SLAVE_PORT_MAX_EV,
- },
- [CCI_IF_MASTER] = {
- CCI400_R0_MASTER_PORT_MIN_EV,
- CCI400_R0_MASTER_PORT_MAX_EV,
- },
- },
- .validate_hw_event = cci400_validate_hw_event,
- .get_event_idx = cci400_get_event_idx,
- },
- [CCI400_R1] = {
- .name = "CCI_400_r1",
- .fixed_hw_cntrs = 1, /* Cycle counter */
- .num_hw_cntrs = 4,
- .cntr_size = SZ_4K,
- .format_attrs = cci400_pmu_format_attrs,
- .event_attrs = cci400_r1_pmu_event_attrs,
- .event_ranges = {
- [CCI_IF_SLAVE] = {
- CCI400_R1_SLAVE_PORT_MIN_EV,
- CCI400_R1_SLAVE_PORT_MAX_EV,
- },
- [CCI_IF_MASTER] = {
- CCI400_R1_MASTER_PORT_MIN_EV,
- CCI400_R1_MASTER_PORT_MAX_EV,
- },
- },
- .validate_hw_event = cci400_validate_hw_event,
- .get_event_idx = cci400_get_event_idx,
- },
-#endif
-#ifdef CONFIG_ARM_CCI5xx_PMU
- [CCI500_R0] = {
- .name = "CCI_500",
- .fixed_hw_cntrs = 0,
- .num_hw_cntrs = 8,
- .cntr_size = SZ_64K,
- .format_attrs = cci5xx_pmu_format_attrs,
- .event_attrs = cci5xx_pmu_event_attrs,
- .event_ranges = {
- [CCI_IF_SLAVE] = {
- CCI5xx_SLAVE_PORT_MIN_EV,
- CCI5xx_SLAVE_PORT_MAX_EV,
- },
- [CCI_IF_MASTER] = {
- CCI5xx_MASTER_PORT_MIN_EV,
- CCI5xx_MASTER_PORT_MAX_EV,
- },
- [CCI_IF_GLOBAL] = {
- CCI5xx_GLOBAL_PORT_MIN_EV,
- CCI5xx_GLOBAL_PORT_MAX_EV,
- },
- },
- .validate_hw_event = cci500_validate_hw_event,
- .write_counters = cci5xx_pmu_write_counters,
- },
- [CCI550_R0] = {
- .name = "CCI_550",
- .fixed_hw_cntrs = 0,
- .num_hw_cntrs = 8,
- .cntr_size = SZ_64K,
- .format_attrs = cci5xx_pmu_format_attrs,
- .event_attrs = cci5xx_pmu_event_attrs,
- .event_ranges = {
- [CCI_IF_SLAVE] = {
- CCI5xx_SLAVE_PORT_MIN_EV,
- CCI5xx_SLAVE_PORT_MAX_EV,
- },
- [CCI_IF_MASTER] = {
- CCI5xx_MASTER_PORT_MIN_EV,
- CCI5xx_MASTER_PORT_MAX_EV,
- },
- [CCI_IF_GLOBAL] = {
- CCI5xx_GLOBAL_PORT_MIN_EV,
- CCI5xx_GLOBAL_PORT_MAX_EV,
- },
- },
- .validate_hw_event = cci550_validate_hw_event,
- .write_counters = cci5xx_pmu_write_counters,
- },
-#endif
-};
-
-static const struct of_device_id arm_cci_pmu_matches[] = {
-#ifdef CONFIG_ARM_CCI400_PMU
- {
- .compatible = "arm,cci-400-pmu",
- .data = NULL,
- },
- {
- .compatible = "arm,cci-400-pmu,r0",
- .data = &cci_pmu_models[CCI400_R0],
- },
- {
- .compatible = "arm,cci-400-pmu,r1",
- .data = &cci_pmu_models[CCI400_R1],
- },
-#endif
-#ifdef CONFIG_ARM_CCI5xx_PMU
- {
- .compatible = "arm,cci-500-pmu,r0",
- .data = &cci_pmu_models[CCI500_R0],
- },
- {
- .compatible = "arm,cci-550-pmu,r0",
- .data = &cci_pmu_models[CCI550_R0],
- },
-#endif
- {},
+static const struct of_dev_auxdata arm_cci_auxdata[] = {
+ OF_DEV_AUXDATA("arm,cci-400-pmu", 0, NULL, &cci_ctrl_base),
+ OF_DEV_AUXDATA("arm,cci-400-pmu,r0", 0, NULL, &cci_ctrl_base),
+ OF_DEV_AUXDATA("arm,cci-400-pmu,r1", 0, NULL, &cci_ctrl_base),
+ OF_DEV_AUXDATA("arm,cci-500-pmu,r0", 0, NULL, &cci_ctrl_base),
+ OF_DEV_AUXDATA("arm,cci-550-pmu,r0", 0, NULL, &cci_ctrl_base),
+ {}
};
-static inline const struct cci_pmu_model *get_cci_model(struct platform_device *pdev)
-{
- const struct of_device_id *match = of_match_node(arm_cci_pmu_matches,
- pdev->dev.of_node);
- if (!match)
- return NULL;
- if (match->data)
- return match->data;
-
- dev_warn(&pdev->dev, "DEPRECATED compatible property,"
- "requires secure access to CCI registers");
- return probe_cci_model(pdev);
-}
-
-static bool is_duplicate_irq(int irq, int *irqs, int nr_irqs)
-{
- int i;
-
- for (i = 0; i < nr_irqs; i++)
- if (irq == irqs[i])
- return true;
-
- return false;
-}
-
-static struct cci_pmu *cci_pmu_alloc(struct platform_device *pdev)
-{
- struct cci_pmu *cci_pmu;
- const struct cci_pmu_model *model;
-
- /*
- * All allocations are devm_* hence we don't have to free
- * them explicitly on an error, as it would end up in driver
- * detach.
- */
- model = get_cci_model(pdev);
- if (!model) {
- dev_warn(&pdev->dev, "CCI PMU version not supported\n");
- return ERR_PTR(-ENODEV);
- }
-
- cci_pmu = devm_kzalloc(&pdev->dev, sizeof(*cci_pmu), GFP_KERNEL);
- if (!cci_pmu)
- return ERR_PTR(-ENOMEM);
-
- cci_pmu->model = model;
- cci_pmu->irqs = devm_kcalloc(&pdev->dev, CCI_PMU_MAX_HW_CNTRS(model),
- sizeof(*cci_pmu->irqs), GFP_KERNEL);
- if (!cci_pmu->irqs)
- return ERR_PTR(-ENOMEM);
- cci_pmu->hw_events.events = devm_kcalloc(&pdev->dev,
- CCI_PMU_MAX_HW_CNTRS(model),
- sizeof(*cci_pmu->hw_events.events),
- GFP_KERNEL);
- if (!cci_pmu->hw_events.events)
- return ERR_PTR(-ENOMEM);
- cci_pmu->hw_events.used_mask = devm_kcalloc(&pdev->dev,
- BITS_TO_LONGS(CCI_PMU_MAX_HW_CNTRS(model)),
- sizeof(*cci_pmu->hw_events.used_mask),
- GFP_KERNEL);
- if (!cci_pmu->hw_events.used_mask)
- return ERR_PTR(-ENOMEM);
-
- return cci_pmu;
-}
-
-
-static int cci_pmu_probe(struct platform_device *pdev)
-{
- struct resource *res;
- struct cci_pmu *cci_pmu;
- int i, ret, irq;
-
- cci_pmu = cci_pmu_alloc(pdev);
- if (IS_ERR(cci_pmu))
- return PTR_ERR(cci_pmu);
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- cci_pmu->base = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(cci_pmu->base))
- return -ENOMEM;
-
- /*
- * CCI PMU has one overflow interrupt per counter; but some may be tied
- * together to a common interrupt.
- */
- cci_pmu->nr_irqs = 0;
- for (i = 0; i < CCI_PMU_MAX_HW_CNTRS(cci_pmu->model); i++) {
- irq = platform_get_irq(pdev, i);
- if (irq < 0)
- break;
-
- if (is_duplicate_irq(irq, cci_pmu->irqs, cci_pmu->nr_irqs))
- continue;
-
- cci_pmu->irqs[cci_pmu->nr_irqs++] = irq;
- }
-
- /*
- * Ensure that the device tree has as many interrupts as the number
- * of counters.
- */
- if (i < CCI_PMU_MAX_HW_CNTRS(cci_pmu->model)) {
- dev_warn(&pdev->dev, "In-correct number of interrupts: %d, should be %d\n",
- i, CCI_PMU_MAX_HW_CNTRS(cci_pmu->model));
- return -EINVAL;
- }
-
- raw_spin_lock_init(&cci_pmu->hw_events.pmu_lock);
- mutex_init(&cci_pmu->reserve_mutex);
- atomic_set(&cci_pmu->active_events, 0);
- cpumask_set_cpu(get_cpu(), &cci_pmu->cpus);
-
- ret = cci_pmu_init(cci_pmu, pdev);
- if (ret) {
- put_cpu();
- return ret;
- }
-
- cpuhp_state_add_instance_nocalls(CPUHP_AP_PERF_ARM_CCI_ONLINE,
- &cci_pmu->node);
- put_cpu();
- pr_info("ARM %s PMU driver probed", cci_pmu->model->name);
- return 0;
-}
+#define DRIVER_NAME "ARM-CCI"
static int cci_platform_probe(struct platform_device *pdev)
{
if (!cci_probed())
return -ENODEV;
- return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+ return of_platform_populate(pdev->dev.of_node, NULL,
+ arm_cci_auxdata, &pdev->dev);
}
-static struct platform_driver cci_pmu_driver = {
- .driver = {
- .name = DRIVER_NAME_PMU,
- .of_match_table = arm_cci_pmu_matches,
- },
- .probe = cci_pmu_probe,
-};
-
static struct platform_driver cci_platform_driver = {
.driver = {
.name = DRIVER_NAME,
@@ -1796,30 +85,9 @@ static struct platform_driver cci_platform_driver = {
static int __init cci_platform_init(void)
{
- int ret;
-
- ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_CCI_ONLINE,
- "perf/arm/cci:online", NULL,
- cci_pmu_offline_cpu);
- if (ret)
- return ret;
-
- ret = platform_driver_register(&cci_pmu_driver);
- if (ret)
- return ret;
-
return platform_driver_register(&cci_platform_driver);
}
-#else /* !CONFIG_ARM_CCI_PMU */
-
-static int __init cci_platform_init(void)
-{
- return 0;
-}
-
-#endif /* CONFIG_ARM_CCI_PMU */
-
#ifdef CONFIG_ARM_CCI400_PORT_CTRL
#define CCI_PORT_CTRL 0x0
@@ -2189,13 +457,10 @@ static int cci_probe_ports(struct device_node *np)
if (!ports)
return -ENOMEM;
- for_each_child_of_node(np, cp) {
+ for_each_available_child_of_node(np, cp) {
if (!of_match_node(arm_cci_ctrl_if_matches, cp))
continue;
- if (!of_device_is_available(cp))
- continue;
-
i = nb_ace + nb_ace_lite;
if (i >= nb_cci_ports)
@@ -2275,7 +540,7 @@ static int cci_probe(void)
struct resource res;
np = of_find_matching_node(NULL, arm_cci_matches);
- if(!np || !of_device_is_available(np))
+ if (!of_device_is_available(np))
return -ENODEV;
ret = of_address_to_resource(np, 0, &res);
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 98ce9fc6e6c0..7ae23b25b406 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -62,6 +62,16 @@ config COMMON_CLK_HI655X
multi-function device has one fixed-rate oscillator, clocked
at 32KHz.
+config COMMON_CLK_SCMI
+ tristate "Clock driver controlled via SCMI interface"
+ depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
+ ---help---
+ This driver provides support for clocks that are controlled
+ by firmware that implements the SCMI interface.
+
+ This driver uses SCMI Message Protocol to interact with the
+ firmware providing all the clock controls.
+
config COMMON_CLK_SCPI
tristate "Clock driver controlled via SCPI interface"
depends on ARM_SCPI_PROTOCOL || COMPILE_TEST
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 71ec41e6364f..6605513eaa94 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o
obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o
obj-$(CONFIG_COMMON_CLK_HI655X) += clk-hi655x.o
obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
+obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o
obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o
obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o
diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
new file mode 100644
index 000000000000..488c21376b55
--- /dev/null
+++ b/drivers/clk/clk-scmi.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Power Interface (SCMI) Protocol based clock driver
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/scmi_protocol.h>
+#include <asm/div64.h>
+
+struct scmi_clk {
+ u32 id;
+ struct clk_hw hw;
+ const struct scmi_clock_info *info;
+ const struct scmi_handle *handle;
+};
+
+#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw)
+
+static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ int ret;
+ u64 rate;
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ ret = clk->handle->clk_ops->rate_get(clk->handle, clk->id, &rate);
+ if (ret)
+ return 0;
+ return rate;
+}
+
+static long scmi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ int step;
+ u64 fmin, fmax, ftmp;
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ /*
+ * We can't figure out what rate it will be, so just return the
+ * rate back to the caller. scmi_clk_recalc_rate() will be called
+ * after the rate is set and we'll know what rate the clock is
+ * running at then.
+ */
+ if (clk->info->rate_discrete)
+ return rate;
+
+ fmin = clk->info->range.min_rate;
+ fmax = clk->info->range.max_rate;
+ if (rate <= fmin)
+ return fmin;
+ else if (rate >= fmax)
+ return fmax;
+
+ ftmp = rate - fmin;
+ ftmp += clk->info->range.step_size - 1; /* to round up */
+ step = do_div(ftmp, clk->info->range.step_size);
+
+ return step * clk->info->range.step_size + fmin;
+}
+
+static int scmi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ return clk->handle->clk_ops->rate_set(clk->handle, clk->id, 0, rate);
+}
+
+static int scmi_clk_enable(struct clk_hw *hw)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ return clk->handle->clk_ops->enable(clk->handle, clk->id);
+}
+
+static void scmi_clk_disable(struct clk_hw *hw)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ clk->handle->clk_ops->disable(clk->handle, clk->id);
+}
+
+static const struct clk_ops scmi_clk_ops = {
+ .recalc_rate = scmi_clk_recalc_rate,
+ .round_rate = scmi_clk_round_rate,
+ .set_rate = scmi_clk_set_rate,
+ /*
+ * We can't provide enable/disable callback as we can't perform the same
+ * in atomic context. Since the clock framework provides standard API
+ * clk_prepare_enable that helps cases using clk_enable in non-atomic
+ * context, it should be fine providing prepare/unprepare.
+ */
+ .prepare = scmi_clk_enable,
+ .unprepare = scmi_clk_disable,
+};
+
+static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk)
+{
+ int ret;
+ struct clk_init_data init = {
+ .flags = CLK_GET_RATE_NOCACHE,
+ .num_parents = 0,
+ .ops = &scmi_clk_ops,
+ .name = sclk->info->name,
+ };
+
+ sclk->hw.init = &init;
+ ret = devm_clk_hw_register(dev, &sclk->hw);
+ if (!ret)
+ clk_hw_set_rate_range(&sclk->hw, sclk->info->range.min_rate,
+ sclk->info->range.max_rate);
+ return ret;
+}
+
+static int scmi_clocks_probe(struct scmi_device *sdev)
+{
+ int idx, count, err;
+ struct clk_hw **hws;
+ struct clk_hw_onecell_data *clk_data;
+ struct device *dev = &sdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct scmi_handle *handle = sdev->handle;
+
+ if (!handle || !handle->clk_ops)
+ return -ENODEV;
+
+ count = handle->clk_ops->count_get(handle);
+ if (count < 0) {
+ dev_err(dev, "%s: invalid clock output count\n", np->name);
+ return -EINVAL;
+ }
+
+ clk_data = devm_kzalloc(dev, sizeof(*clk_data) +
+ sizeof(*clk_data->hws) * count, GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->num = count;
+ hws = clk_data->hws;
+
+ for (idx = 0; idx < count; idx++) {
+ struct scmi_clk *sclk;
+
+ sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);
+ if (!sclk)
+ return -ENOMEM;
+
+ sclk->info = handle->clk_ops->info_get(handle, idx);
+ if (!sclk->info) {
+ dev_dbg(dev, "invalid clock info for idx %d\n", idx);
+ continue;
+ }
+
+ sclk->id = idx;
+ sclk->handle = handle;
+
+ err = scmi_clk_ops_init(dev, sclk);
+ if (err) {
+ dev_err(dev, "failed to register clock %d\n", idx);
+ devm_kfree(dev, sclk);
+ hws[idx] = NULL;
+ } else {
+ dev_dbg(dev, "Registered clock:%s\n", sclk->info->name);
+ hws[idx] = &sclk->hw;
+ }
+ }
+
+ return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
+ clk_data);
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+ { SCMI_PROTOCOL_CLOCK },
+ { },
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_clocks_driver = {
+ .name = "scmi-clocks",
+ .probe = scmi_clocks_probe,
+ .id_table = scmi_id_table,
+};
+module_scmi_driver(scmi_clocks_driver);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI clock driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 833b5f41f596..7f56fe5183f2 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -239,6 +239,18 @@ config ARM_SA1100_CPUFREQ
config ARM_SA1110_CPUFREQ
bool
+config ARM_SCMI_CPUFREQ
+ tristate "SCMI based CPUfreq driver"
+ depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
+ depends on !CPU_THERMAL || THERMAL
+ select PM_OPP
+ help
+ This adds the CPUfreq driver support for ARM platforms using SCMI
+ protocol for CPU power management.
+
+ This driver uses SCMI Message Protocol driver to interact with the
+ firmware providing the CPU DVFS functionality.
+
config ARM_SPEAR_CPUFREQ
bool "SPEAr CPUFreq support"
depends on PLAT_SPEAR
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index b2cbdc016c77..8d24ade3bd02 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -75,6 +75,7 @@ obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o
obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o
obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o
+obj-$(CONFIG_ARM_SCMI_CPUFREQ) += scmi-cpufreq.o
obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o
obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
obj-$(CONFIG_ARM_STI_CPUFREQ) += sti-cpufreq.o
diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
new file mode 100644
index 000000000000..959a1dbe3835
--- /dev/null
+++ b/drivers/cpufreq/scmi-cpufreq.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Power Interface (SCMI) based CPUFreq Interface driver
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ * Sudeep Holla <sudeep.holla@arm.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/cpu_cooling.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/scmi_protocol.h>
+#include <linux/types.h>
+
+struct scmi_data {
+ int domain_id;
+ struct device *cpu_dev;
+ struct thermal_cooling_device *cdev;
+};
+
+static const struct scmi_handle *handle;
+
+static unsigned int scmi_cpufreq_get_rate(unsigned int cpu)
+{
+ struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
+ struct scmi_perf_ops *perf_ops = handle->perf_ops;
+ struct scmi_data *priv = policy->driver_data;
+ unsigned long rate;
+ int ret;
+
+ ret = perf_ops->freq_get(handle, priv->domain_id, &rate, false);
+ if (ret)
+ return 0;
+ return rate / 1000;
+}
+
+/*
+ * perf_ops->freq_set is not a synchronous, the actual OPP change will
+ * happen asynchronously and can get notified if the events are
+ * subscribed for by the SCMI firmware
+ */
+static int
+scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
+{
+ int ret;
+ struct scmi_data *priv = policy->driver_data;
+ struct scmi_perf_ops *perf_ops = handle->perf_ops;
+ u64 freq = policy->freq_table[index].frequency * 1000;
+
+ ret = perf_ops->freq_set(handle, priv->domain_id, freq, false);
+ if (!ret)
+ arch_set_freq_scale(policy->related_cpus, freq,
+ policy->cpuinfo.max_freq);
+ return ret;
+}
+
+static unsigned int scmi_cpufreq_fast_switch(struct cpufreq_policy *policy,
+ unsigned int target_freq)
+{
+ struct scmi_data *priv = policy->driver_data;
+ struct scmi_perf_ops *perf_ops = handle->perf_ops;
+
+ if (!perf_ops->freq_set(handle, priv->domain_id,
+ target_freq * 1000, true)) {
+ arch_set_freq_scale(policy->related_cpus, target_freq,
+ policy->cpuinfo.max_freq);
+ return target_freq;
+ }
+
+ return 0;
+}
+
+static int
+scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
+{
+ int cpu, domain, tdomain;
+ struct device *tcpu_dev;
+
+ domain = handle->perf_ops->device_domain_id(cpu_dev);
+ if (domain < 0)
+ return domain;
+
+ for_each_possible_cpu(cpu) {
+ if (cpu == cpu_dev->id)
+ continue;
+
+ tcpu_dev = get_cpu_device(cpu);
+ if (!tcpu_dev)
+ continue;
+
+ tdomain = handle->perf_ops->device_domain_id(tcpu_dev);
+ if (tdomain == domain)
+ cpumask_set_cpu(cpu, cpumask);
+ }
+
+ return 0;
+}
+
+static int scmi_cpufreq_init(struct cpufreq_policy *policy)
+{
+ int ret;
+ unsigned int latency;
+ struct device *cpu_dev;
+ struct scmi_data *priv;
+ struct cpufreq_frequency_table *freq_table;
+
+ cpu_dev = get_cpu_device(policy->cpu);
+ if (!cpu_dev) {
+ pr_err("failed to get cpu%d device\n", policy->cpu);
+ return -ENODEV;
+ }
+
+ ret = handle->perf_ops->add_opps_to_device(handle, cpu_dev);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to add opps to the device\n");
+ return ret;
+ }
+
+ ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to get sharing cpumask\n");
+ return ret;
+ }
+
+ ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus);
+ if (ret) {
+ dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = dev_pm_opp_get_opp_count(cpu_dev);
+ if (ret <= 0) {
+ dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n");
+ ret = -EPROBE_DEFER;
+ goto out_free_opp;
+ }
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto out_free_opp;
+ }
+
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
+ goto out_free_priv;
+ }
+
+ priv->cpu_dev = cpu_dev;
+ priv->domain_id = handle->perf_ops->device_domain_id(cpu_dev);
+
+ policy->driver_data = priv;
+
+ ret = cpufreq_table_validate_and_show(policy, freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "%s: invalid frequency table: %d\n", __func__,
+ ret);
+ goto out_free_cpufreq_table;
+ }
+
+ /* SCMI allows DVFS request for any domain from any CPU */
+ policy->dvfs_possible_from_any_cpu = true;
+
+ latency = handle->perf_ops->get_transition_latency(handle, cpu_dev);
+ if (!latency)
+ latency = CPUFREQ_ETERNAL;
+
+ policy->cpuinfo.transition_latency = latency;
+
+ policy->fast_switch_possible = true;
+ return 0;
+
+out_free_cpufreq_table:
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
+out_free_priv:
+ kfree(priv);
+out_free_opp:
+ dev_pm_opp_cpumask_remove_table(policy->cpus);
+
+ return ret;
+}
+
+static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
+{
+ struct scmi_data *priv = policy->driver_data;
+
+ cpufreq_cooling_unregister(priv->cdev);
+ dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
+ kfree(priv);
+ dev_pm_opp_cpumask_remove_table(policy->related_cpus);
+
+ return 0;
+}
+
+static void scmi_cpufreq_ready(struct cpufreq_policy *policy)
+{
+ struct scmi_data *priv = policy->driver_data;
+
+ priv->cdev = of_cpufreq_cooling_register(policy);
+}
+
+static struct cpufreq_driver scmi_cpufreq_driver = {
+ .name = "scmi",
+ .flags = CPUFREQ_STICKY | CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
+ CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+ .verify = cpufreq_generic_frequency_table_verify,
+ .attr = cpufreq_generic_attr,
+ .target_index = scmi_cpufreq_set_target,
+ .fast_switch = scmi_cpufreq_fast_switch,
+ .get = scmi_cpufreq_get_rate,
+ .init = scmi_cpufreq_init,
+ .exit = scmi_cpufreq_exit,
+ .ready = scmi_cpufreq_ready,
+};
+
+static int scmi_cpufreq_probe(struct scmi_device *sdev)
+{
+ int ret;
+
+ handle = sdev->handle;
+
+ if (!handle || !handle->perf_ops)
+ return -ENODEV;
+
+ ret = cpufreq_register_driver(&scmi_cpufreq_driver);
+ if (ret) {
+ dev_err(&sdev->dev, "%s: registering cpufreq failed, err: %d\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static void scmi_cpufreq_remove(struct scmi_device *sdev)
+{
+ cpufreq_unregister_driver(&scmi_cpufreq_driver);
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+ { SCMI_PROTOCOL_PERF },
+ { },
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_cpufreq_drv = {
+ .name = "scmi-cpufreq",
+ .probe = scmi_cpufreq_probe,
+ .remove = scmi_cpufreq_remove,
+ .id_table = scmi_id_table,
+};
+module_scmi_driver(scmi_cpufreq_drv);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI CPUFreq interface driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index b7c748248e53..6e83880046d7 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -19,6 +19,40 @@ config ARM_PSCI_CHECKER
on and off through hotplug, so for now torture tests and PSCI checker
are mutually exclusive.
+config ARM_SCMI_PROTOCOL
+ bool "ARM System Control and Management Interface (SCMI) Message Protocol"
+ depends on ARM || ARM64 || COMPILE_TEST
+ depends on MAILBOX
+ help
+ ARM System Control and Management Interface (SCMI) protocol is a
+ set of operating system-independent software interfaces that are
+ used in system management. SCMI is extensible and currently provides
+ interfaces for: Discovery and self-description of the interfaces
+ it supports, Power domain management which is the ability to place
+ a given device or domain into the various power-saving states that
+ it supports, Performance management which is the ability to control
+ the performance of a domain that is composed of compute engines
+ such as application processors and other accelerators, Clock
+ management which is the ability to set and inquire rates on platform
+ managed clocks and Sensor management which is the ability to read
+ sensor data, and be notified of sensor value.
+
+ This protocol library provides interface for all the client drivers
+ making use of the features offered by the SCMI.
+
+config ARM_SCMI_POWER_DOMAIN
+ tristate "SCMI power domain driver"
+ depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+ default y
+ select PM_GENERIC_DOMAINS if PM
+ help
+ This enables support for the SCMI power domains which can be
+ enabled or disabled via the SCP firmware
+
+ This driver can also be built as a module. If so, the module
+ will be called scmi_pm_domain. Note this may needed early in boot
+ before rootfs may be available.
+
config ARM_SCPI_PROTOCOL
tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
depends on ARM || ARM64 || COMPILE_TEST
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index b248238ddc6a..e18a041cfc53 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_QCOM_SCM_32) += qcom_scm-32.o
CFLAGS_qcom_scm-32.o :=$(call as-instr,.arch armv7-a\n.arch_extension sec,-DREQUIRES_SEC=1) -march=armv7-a
obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
+obj-$(CONFIG_ARM_SCMI_PROTOCOL) += arm_scmi/
obj-y += broadcom/
obj-y += meson/
obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
new file mode 100644
index 000000000000..99e36c580fbc
--- /dev/null
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -0,0 +1,5 @@
+obj-y = scmi-bus.o scmi-driver.o scmi-protocols.o
+scmi-bus-y = bus.o
+scmi-driver-y = driver.o
+scmi-protocols-y = base.o clock.o perf.o power.o sensors.o
+obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c
new file mode 100644
index 000000000000..0d3806c0d432
--- /dev/null
+++ b/drivers/firmware/arm_scmi/base.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Base Protocol
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include "common.h"
+
+enum scmi_base_protocol_cmd {
+ BASE_DISCOVER_VENDOR = 0x3,
+ BASE_DISCOVER_SUB_VENDOR = 0x4,
+ BASE_DISCOVER_IMPLEMENT_VERSION = 0x5,
+ BASE_DISCOVER_LIST_PROTOCOLS = 0x6,
+ BASE_DISCOVER_AGENT = 0x7,
+ BASE_NOTIFY_ERRORS = 0x8,
+};
+
+struct scmi_msg_resp_base_attributes {
+ u8 num_protocols;
+ u8 num_agents;
+ __le16 reserved;
+};
+
+/**
+ * scmi_base_attributes_get() - gets the implementation details
+ * that are associated with the base protocol.
+ *
+ * @handle - SCMI entity handle
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_attributes_get(const struct scmi_handle *handle)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_base_attributes *attr_info;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_BASE, 0, sizeof(*attr_info), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ attr_info = t->rx.buf;
+ rev->num_protocols = attr_info->num_protocols;
+ rev->num_agents = attr_info->num_agents;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_vendor_id_get() - gets vendor/subvendor identifier ASCII string.
+ *
+ * @handle - SCMI entity handle
+ * @sub_vendor - specify true if sub-vendor ID is needed
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int
+scmi_base_vendor_id_get(const struct scmi_handle *handle, bool sub_vendor)
+{
+ u8 cmd;
+ int ret, size;
+ char *vendor_id;
+ struct scmi_xfer *t;
+ struct scmi_revision_info *rev = handle->version;
+
+ if (sub_vendor) {
+ cmd = BASE_DISCOVER_SUB_VENDOR;
+ vendor_id = rev->sub_vendor_id;
+ size = ARRAY_SIZE(rev->sub_vendor_id);
+ } else {
+ cmd = BASE_DISCOVER_VENDOR;
+ vendor_id = rev->vendor_id;
+ size = ARRAY_SIZE(rev->vendor_id);
+ }
+
+ ret = scmi_one_xfer_init(handle, cmd, SCMI_PROTOCOL_BASE, 0, size, &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(vendor_id, t->rx.buf, size);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_implementation_version_get() - gets a vendor-specific
+ * implementation 32-bit version. The format of the version number is
+ * vendor-specific
+ *
+ * @handle - SCMI entity handle
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int
+scmi_base_implementation_version_get(const struct scmi_handle *handle)
+{
+ int ret;
+ __le32 *impl_ver;
+ struct scmi_xfer *t;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_IMPLEMENT_VERSION,
+ SCMI_PROTOCOL_BASE, 0, sizeof(*impl_ver), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ impl_ver = t->rx.buf;
+ rev->impl_ver = le32_to_cpu(*impl_ver);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_implementation_list_get() - gets the list of protocols it is
+ * OSPM is allowed to access
+ *
+ * @handle - SCMI entity handle
+ * @protocols_imp - pointer to hold the list of protocol identifiers
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_implementation_list_get(const struct scmi_handle *handle,
+ u8 *protocols_imp)
+{
+ u8 *list;
+ int ret, loop;
+ struct scmi_xfer *t;
+ __le32 *num_skip, *num_ret;
+ u32 tot_num_ret = 0, loop_num_ret;
+ struct device *dev = handle->dev;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_LIST_PROTOCOLS,
+ SCMI_PROTOCOL_BASE, sizeof(*num_skip), 0, &t);
+ if (ret)
+ return ret;
+
+ num_skip = t->tx.buf;
+ num_ret = t->rx.buf;
+ list = t->rx.buf + sizeof(*num_ret);
+
+ do {
+ /* Set the number of protocols to be skipped/already read */
+ *num_skip = cpu_to_le32(tot_num_ret);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ loop_num_ret = le32_to_cpu(*num_ret);
+ if (tot_num_ret + loop_num_ret > MAX_PROTOCOLS_IMP) {
+ dev_err(dev, "No. of Protocol > MAX_PROTOCOLS_IMP");
+ break;
+ }
+
+ for (loop = 0; loop < loop_num_ret; loop++)
+ protocols_imp[tot_num_ret + loop] = *(list + loop);
+
+ tot_num_ret += loop_num_ret;
+ } while (loop_num_ret);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_discover_agent_get() - discover the name of an agent
+ *
+ * @handle - SCMI entity handle
+ * @id - Agent identifier
+ * @name - Agent identifier ASCII string
+ *
+ * An agent id of 0 is reserved to identify the platform itself.
+ * Generally operating system is represented as "OSPM"
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_discover_agent_get(const struct scmi_handle *handle,
+ int id, char *name)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_AGENT,
+ SCMI_PROTOCOL_BASE, sizeof(__le32),
+ SCMI_MAX_STR_SIZE, &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(id);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+int scmi_base_protocol_init(struct scmi_handle *h)
+{
+ int id, ret;
+ u8 *prot_imp;
+ u32 version;
+ char name[SCMI_MAX_STR_SIZE];
+ const struct scmi_handle *handle = h;
+ struct device *dev = handle->dev;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_version_get(handle, SCMI_PROTOCOL_BASE, &version);
+ if (ret)
+ return ret;
+
+ prot_imp = devm_kcalloc(dev, MAX_PROTOCOLS_IMP, sizeof(u8), GFP_KERNEL);
+ if (!prot_imp)
+ return -ENOMEM;
+
+ rev->major_ver = PROTOCOL_REV_MAJOR(version),
+ rev->minor_ver = PROTOCOL_REV_MINOR(version);
+
+ scmi_base_attributes_get(handle);
+ scmi_base_vendor_id_get(handle, false);
+ scmi_base_vendor_id_get(handle, true);
+ scmi_base_implementation_version_get(handle);
+ scmi_base_implementation_list_get(handle, prot_imp);
+ scmi_setup_protocol_implemented(handle, prot_imp);
+
+ dev_info(dev, "SCMI Protocol v%d.%d '%s:%s' Firmware version 0x%x\n",
+ rev->major_ver, rev->minor_ver, rev->vendor_id,
+ rev->sub_vendor_id, rev->impl_ver);
+ dev_dbg(dev, "Found %d protocol(s) %d agent(s)\n", rev->num_protocols,
+ rev->num_agents);
+
+ for (id = 0; id < rev->num_agents; id++) {
+ scmi_base_discover_agent_get(handle, id, name);
+ dev_dbg(dev, "Agent %d: %s\n", id, name);
+ }
+
+ return 0;
+}
diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c
new file mode 100644
index 000000000000..f2760a596c28
--- /dev/null
+++ b/drivers/firmware/arm_scmi/bus.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Message Protocol bus layer
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#include "common.h"
+
+static DEFINE_IDA(scmi_bus_id);
+static DEFINE_IDR(scmi_protocols);
+static DEFINE_SPINLOCK(protocol_lock);
+
+static const struct scmi_device_id *
+scmi_dev_match_id(struct scmi_device *scmi_dev, struct scmi_driver *scmi_drv)
+{
+ const struct scmi_device_id *id = scmi_drv->id_table;
+
+ if (!id)
+ return NULL;
+
+ for (; id->protocol_id; id++)
+ if (id->protocol_id == scmi_dev->protocol_id)
+ return id;
+
+ return NULL;
+}
+
+static int scmi_dev_match(struct device *dev, struct device_driver *drv)
+{
+ struct scmi_driver *scmi_drv = to_scmi_driver(drv);
+ struct scmi_device *scmi_dev = to_scmi_dev(dev);
+ const struct scmi_device_id *id;
+
+ id = scmi_dev_match_id(scmi_dev, scmi_drv);
+ if (id)
+ return 1;
+
+ return 0;
+}
+
+static int scmi_protocol_init(int protocol_id, struct scmi_handle *handle)
+{
+ scmi_prot_init_fn_t fn = idr_find(&scmi_protocols, protocol_id);
+
+ if (unlikely(!fn))
+ return -EINVAL;
+ return fn(handle);
+}
+
+static int scmi_dev_probe(struct device *dev)
+{
+ struct scmi_driver *scmi_drv = to_scmi_driver(dev->driver);
+ struct scmi_device *scmi_dev = to_scmi_dev(dev);
+ const struct scmi_device_id *id;
+ int ret;
+
+ id = scmi_dev_match_id(scmi_dev, scmi_drv);
+ if (!id)
+ return -ENODEV;
+
+ if (!scmi_dev->handle)
+ return -EPROBE_DEFER;
+
+ ret = scmi_protocol_init(scmi_dev->protocol_id, scmi_dev->handle);
+ if (ret)
+ return ret;
+
+ return scmi_drv->probe(scmi_dev);
+}
+
+static int scmi_dev_remove(struct device *dev)
+{
+ struct scmi_driver *scmi_drv = to_scmi_driver(dev->driver);
+ struct scmi_device *scmi_dev = to_scmi_dev(dev);
+
+ if (scmi_drv->remove)
+ scmi_drv->remove(scmi_dev);
+
+ return 0;
+}
+
+static struct bus_type scmi_bus_type = {
+ .name = "scmi_protocol",
+ .match = scmi_dev_match,
+ .probe = scmi_dev_probe,
+ .remove = scmi_dev_remove,
+};
+
+int scmi_driver_register(struct scmi_driver *driver, struct module *owner,
+ const char *mod_name)
+{
+ int retval;
+
+ driver->driver.bus = &scmi_bus_type;
+ driver->driver.name = driver->name;
+ driver->driver.owner = owner;
+ driver->driver.mod_name = mod_name;
+
+ retval = driver_register(&driver->driver);
+ if (!retval)
+ pr_debug("registered new scmi driver %s\n", driver->name);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(scmi_driver_register);
+
+void scmi_driver_unregister(struct scmi_driver *driver)
+{
+ driver_unregister(&driver->driver);
+}
+EXPORT_SYMBOL_GPL(scmi_driver_unregister);
+
+struct scmi_device *
+scmi_device_create(struct device_node *np, struct device *parent, int protocol)
+{
+ int id, retval;
+ struct scmi_device *scmi_dev;
+
+ id = ida_simple_get(&scmi_bus_id, 1, 0, GFP_KERNEL);
+ if (id < 0)
+ return NULL;
+
+ scmi_dev = kzalloc(sizeof(*scmi_dev), GFP_KERNEL);
+ if (!scmi_dev)
+ goto no_mem;
+
+ scmi_dev->id = id;
+ scmi_dev->protocol_id = protocol;
+ scmi_dev->dev.parent = parent;
+ scmi_dev->dev.of_node = np;
+ scmi_dev->dev.bus = &scmi_bus_type;
+ dev_set_name(&scmi_dev->dev, "scmi_dev.%d", id);
+
+ retval = device_register(&scmi_dev->dev);
+ if (!retval)
+ return scmi_dev;
+
+ put_device(&scmi_dev->dev);
+ kfree(scmi_dev);
+no_mem:
+ ida_simple_remove(&scmi_bus_id, id);
+ return NULL;
+}
+
+void scmi_device_destroy(struct scmi_device *scmi_dev)
+{
+ scmi_handle_put(scmi_dev->handle);
+ device_unregister(&scmi_dev->dev);
+ ida_simple_remove(&scmi_bus_id, scmi_dev->id);
+ kfree(scmi_dev);
+}
+
+void scmi_set_handle(struct scmi_device *scmi_dev)
+{
+ scmi_dev->handle = scmi_handle_get(&scmi_dev->dev);
+}
+
+int scmi_protocol_register(int protocol_id, scmi_prot_init_fn_t fn)
+{
+ int ret;
+
+ spin_lock(&protocol_lock);
+ ret = idr_alloc(&scmi_protocols, fn, protocol_id, protocol_id + 1,
+ GFP_ATOMIC);
+ if (ret != protocol_id)
+ pr_err("unable to allocate SCMI idr slot, err %d\n", ret);
+ spin_unlock(&protocol_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(scmi_protocol_register);
+
+void scmi_protocol_unregister(int protocol_id)
+{
+ spin_lock(&protocol_lock);
+ idr_remove(&scmi_protocols, protocol_id);
+ spin_unlock(&protocol_lock);
+}
+EXPORT_SYMBOL_GPL(scmi_protocol_unregister);
+
+static int __scmi_devices_unregister(struct device *dev, void *data)
+{
+ struct scmi_device *scmi_dev = to_scmi_dev(dev);
+
+ scmi_device_destroy(scmi_dev);
+ return 0;
+}
+
+static void scmi_devices_unregister(void)
+{
+ bus_for_each_dev(&scmi_bus_type, NULL, NULL, __scmi_devices_unregister);
+}
+
+static int __init scmi_bus_init(void)
+{
+ int retval;
+
+ retval = bus_register(&scmi_bus_type);
+ if (retval)
+ pr_err("scmi protocol bus register failed (%d)\n", retval);
+
+ return retval;
+}
+subsys_initcall(scmi_bus_init);
+
+static void __exit scmi_bus_exit(void)
+{
+ scmi_devices_unregister();
+ bus_unregister(&scmi_bus_type);
+ ida_destroy(&scmi_bus_id);
+}
+module_exit(scmi_bus_exit);
diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c
new file mode 100644
index 000000000000..e6f17825db79
--- /dev/null
+++ b/drivers/firmware/arm_scmi/clock.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Clock Protocol
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include "common.h"
+
+enum scmi_clock_protocol_cmd {
+ CLOCK_ATTRIBUTES = 0x3,
+ CLOCK_DESCRIBE_RATES = 0x4,
+ CLOCK_RATE_SET = 0x5,
+ CLOCK_RATE_GET = 0x6,
+ CLOCK_CONFIG_SET = 0x7,
+};
+
+struct scmi_msg_resp_clock_protocol_attributes {
+ __le16 num_clocks;
+ u8 max_async_req;
+ u8 reserved;
+};
+
+struct scmi_msg_resp_clock_attributes {
+ __le32 attributes;
+#define CLOCK_ENABLE BIT(0)
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_clock_set_config {
+ __le32 id;
+ __le32 attributes;
+};
+
+struct scmi_msg_clock_describe_rates {
+ __le32 id;
+ __le32 rate_index;
+};
+
+struct scmi_msg_resp_clock_describe_rates {
+ __le32 num_rates_flags;
+#define NUM_RETURNED(x) ((x) & 0xfff)
+#define RATE_DISCRETE(x) !((x) & BIT(12))
+#define NUM_REMAINING(x) ((x) >> 16)
+ struct {
+ __le32 value_low;
+ __le32 value_high;
+ } rate[0];
+#define RATE_TO_U64(X) \
+({ \
+ typeof(X) x = (X); \
+ le32_to_cpu((x).value_low) | (u64)le32_to_cpu((x).value_high) << 32; \
+})
+};
+
+struct scmi_clock_set_rate {
+ __le32 flags;
+#define CLOCK_SET_ASYNC BIT(0)
+#define CLOCK_SET_DELAYED BIT(1)
+#define CLOCK_SET_ROUND_UP BIT(2)
+#define CLOCK_SET_ROUND_AUTO BIT(3)
+ __le32 id;
+ __le32 value_low;
+ __le32 value_high;
+};
+
+struct clock_info {
+ int num_clocks;
+ int max_async_req;
+ struct scmi_clock_info *clk;
+};
+
+static int scmi_clock_protocol_attributes_get(const struct scmi_handle *handle,
+ struct clock_info *ci)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_clock_protocol_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_CLOCK, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ ci->num_clocks = le16_to_cpu(attr->num_clocks);
+ ci->max_async_req = attr->max_async_req;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_attributes_get(const struct scmi_handle *handle,
+ u32 clk_id, struct scmi_clock_info *clk)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_clock_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_ATTRIBUTES, SCMI_PROTOCOL_CLOCK,
+ sizeof(clk_id), sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(clk_id);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(clk->name, attr->name, SCMI_MAX_STR_SIZE);
+ else
+ clk->name[0] = '\0';
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_describe_rates_get(const struct scmi_handle *handle, u32 clk_id,
+ struct scmi_clock_info *clk)
+{
+ u64 *rate;
+ int ret, cnt;
+ bool rate_discrete = false;
+ u32 tot_rate_cnt = 0, rates_flag;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_msg_clock_describe_rates *clk_desc;
+ struct scmi_msg_resp_clock_describe_rates *rlist;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_DESCRIBE_RATES,
+ SCMI_PROTOCOL_CLOCK, sizeof(*clk_desc), 0, &t);
+ if (ret)
+ return ret;
+
+ clk_desc = t->tx.buf;
+ rlist = t->rx.buf;
+
+ do {
+ clk_desc->id = cpu_to_le32(clk_id);
+ /* Set the number of rates to be skipped/already read */
+ clk_desc->rate_index = cpu_to_le32(tot_rate_cnt);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ goto err;
+
+ rates_flag = le32_to_cpu(rlist->num_rates_flags);
+ num_remaining = NUM_REMAINING(rates_flag);
+ rate_discrete = RATE_DISCRETE(rates_flag);
+ num_returned = NUM_RETURNED(rates_flag);
+
+ if (tot_rate_cnt + num_returned > SCMI_MAX_NUM_RATES) {
+ dev_err(handle->dev, "No. of rates > MAX_NUM_RATES");
+ break;
+ }
+
+ if (!rate_discrete) {
+ clk->range.min_rate = RATE_TO_U64(rlist->rate[0]);
+ clk->range.max_rate = RATE_TO_U64(rlist->rate[1]);
+ clk->range.step_size = RATE_TO_U64(rlist->rate[2]);
+ dev_dbg(handle->dev, "Min %llu Max %llu Step %llu Hz\n",
+ clk->range.min_rate, clk->range.max_rate,
+ clk->range.step_size);
+ break;
+ }
+
+ rate = &clk->list.rates[tot_rate_cnt];
+ for (cnt = 0; cnt < num_returned; cnt++, rate++) {
+ *rate = RATE_TO_U64(rlist->rate[cnt]);
+ dev_dbg(handle->dev, "Rate %llu Hz\n", *rate);
+ }
+
+ tot_rate_cnt += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ if (rate_discrete)
+ clk->list.num_rates = tot_rate_cnt;
+
+err:
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_rate_get(const struct scmi_handle *handle, u32 clk_id, u64 *value)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_RATE_GET, SCMI_PROTOCOL_CLOCK,
+ sizeof(__le32), sizeof(u64), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(clk_id);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ __le32 *pval = t->rx.buf;
+
+ *value = le32_to_cpu(*pval);
+ *value |= (u64)le32_to_cpu(*(pval + 1)) << 32;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_rate_set(const struct scmi_handle *handle, u32 clk_id,
+ u32 config, u64 rate)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_clock_set_rate *cfg;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_RATE_SET, SCMI_PROTOCOL_CLOCK,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->flags = cpu_to_le32(config);
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->value_low = cpu_to_le32(rate & 0xffffffff);
+ cfg->value_high = cpu_to_le32(rate >> 32);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_config_set(const struct scmi_handle *handle, u32 clk_id, u32 config)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_clock_set_config *cfg;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_CONFIG_SET, SCMI_PROTOCOL_CLOCK,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->attributes = cpu_to_le32(config);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_enable(const struct scmi_handle *handle, u32 clk_id)
+{
+ return scmi_clock_config_set(handle, clk_id, CLOCK_ENABLE);
+}
+
+static int scmi_clock_disable(const struct scmi_handle *handle, u32 clk_id)
+{
+ return scmi_clock_config_set(handle, clk_id, 0);
+}
+
+static int scmi_clock_count_get(const struct scmi_handle *handle)
+{
+ struct clock_info *ci = handle->clk_priv;
+
+ return ci->num_clocks;
+}
+
+static const struct scmi_clock_info *
+scmi_clock_info_get(const struct scmi_handle *handle, u32 clk_id)
+{
+ struct clock_info *ci = handle->clk_priv;
+ struct scmi_clock_info *clk = ci->clk + clk_id;
+
+ if (!clk->name || !clk->name[0])
+ return NULL;
+
+ return clk;
+}
+
+static struct scmi_clk_ops clk_ops = {
+ .count_get = scmi_clock_count_get,
+ .info_get = scmi_clock_info_get,
+ .rate_get = scmi_clock_rate_get,
+ .rate_set = scmi_clock_rate_set,
+ .enable = scmi_clock_enable,
+ .disable = scmi_clock_disable,
+};
+
+static int scmi_clock_protocol_init(struct scmi_handle *handle)
+{
+ u32 version;
+ int clkid, ret;
+ struct clock_info *cinfo;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_CLOCK, &version);
+
+ dev_dbg(handle->dev, "Clock Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ cinfo = devm_kzalloc(handle->dev, sizeof(*cinfo), GFP_KERNEL);
+ if (!cinfo)
+ return -ENOMEM;
+
+ scmi_clock_protocol_attributes_get(handle, cinfo);
+
+ cinfo->clk = devm_kcalloc(handle->dev, cinfo->num_clocks,
+ sizeof(*cinfo->clk), GFP_KERNEL);
+ if (!cinfo->clk)
+ return -ENOMEM;
+
+ for (clkid = 0; clkid < cinfo->num_clocks; clkid++) {
+ struct scmi_clock_info *clk = cinfo->clk + clkid;
+
+ ret = scmi_clock_attributes_get(handle, clkid, clk);
+ if (!ret)
+ scmi_clock_describe_rates_get(handle, clkid, clk);
+ }
+
+ handle->clk_ops = &clk_ops;
+ handle->clk_priv = cinfo;
+
+ return 0;
+}
+
+static int __init scmi_clock_init(void)
+{
+ return scmi_protocol_register(SCMI_PROTOCOL_CLOCK,
+ &scmi_clock_protocol_init);
+}
+subsys_initcall(scmi_clock_init);
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
new file mode 100644
index 000000000000..0c30234f9098
--- /dev/null
+++ b/drivers/firmware/arm_scmi/common.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Message Protocol
+ * driver common header file containing some definitions, structures
+ * and function prototypes used in all the different SCMI protocols.
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/scmi_protocol.h>
+#include <linux/types.h>
+
+#define PROTOCOL_REV_MINOR_BITS 16
+#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1)
+#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
+#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
+#define MAX_PROTOCOLS_IMP 16
+#define MAX_OPPS 16
+
+enum scmi_common_cmd {
+ PROTOCOL_VERSION = 0x0,
+ PROTOCOL_ATTRIBUTES = 0x1,
+ PROTOCOL_MESSAGE_ATTRIBUTES = 0x2,
+};
+
+/**
+ * struct scmi_msg_resp_prot_version - Response for a message
+ *
+ * @major_version: Major version of the ABI that firmware supports
+ * @minor_version: Minor version of the ABI that firmware supports
+ *
+ * In general, ABI version changes follow the rule that minor version increments
+ * are backward compatible. Major revision changes in ABI may not be
+ * backward compatible.
+ *
+ * Response to a generic message with message type SCMI_MSG_VERSION
+ */
+struct scmi_msg_resp_prot_version {
+ __le16 minor_version;
+ __le16 major_version;
+};
+
+/**
+ * struct scmi_msg_hdr - Message(Tx/Rx) header
+ *
+ * @id: The identifier of the command being sent
+ * @protocol_id: The identifier of the protocol used to send @id command
+ * @seq: The token to identify the message. when a message/command returns,
+ * the platform returns the whole message header unmodified including
+ * the token.
+ */
+struct scmi_msg_hdr {
+ u8 id;
+ u8 protocol_id;
+ u16 seq;
+ u32 status;
+ bool poll_completion;
+};
+
+/**
+ * struct scmi_msg - Message(Tx/Rx) structure
+ *
+ * @buf: Buffer pointer
+ * @len: Length of data in the Buffer
+ */
+struct scmi_msg {
+ void *buf;
+ size_t len;
+};
+
+/**
+ * struct scmi_xfer - Structure representing a message flow
+ *
+ * @hdr: Transmit message header
+ * @tx: Transmit message
+ * @rx: Receive message, the buffer should be pre-allocated to store
+ * message. If request-ACK protocol is used, we can reuse the same
+ * buffer for the rx path as we use for the tx path.
+ * @done: completion event
+ */
+
+struct scmi_xfer {
+ void *con_priv;
+ struct scmi_msg_hdr hdr;
+ struct scmi_msg tx;
+ struct scmi_msg rx;
+ struct completion done;
+};
+
+void scmi_one_xfer_put(const struct scmi_handle *h, struct scmi_xfer *xfer);
+int scmi_do_xfer(const struct scmi_handle *h, struct scmi_xfer *xfer);
+int scmi_one_xfer_init(const struct scmi_handle *h, u8 msg_id, u8 prot_id,
+ size_t tx_size, size_t rx_size, struct scmi_xfer **p);
+int scmi_handle_put(const struct scmi_handle *handle);
+struct scmi_handle *scmi_handle_get(struct device *dev);
+void scmi_set_handle(struct scmi_device *scmi_dev);
+int scmi_version_get(const struct scmi_handle *h, u8 protocol, u32 *version);
+void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
+ u8 *prot_imp);
+
+int scmi_base_protocol_init(struct scmi_handle *h);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
new file mode 100644
index 000000000000..14b147135a0c
--- /dev/null
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -0,0 +1,871 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Message Protocol driver
+ *
+ * SCMI Message Protocol is used between the System Control Processor(SCP)
+ * and the Application Processors(AP). The Message Handling Unit(MHU)
+ * provides a mechanism for inter-processor communication between SCP's
+ * Cortex M3 and AP.
+ *
+ * SCP offers control and management of the core/cluster power states,
+ * various power domain DVFS including the core/cluster, certain system
+ * clocks configuration, thermal sensors and many others.
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/processor.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+
+#include "common.h"
+
+#define MSG_ID_SHIFT 0
+#define MSG_ID_MASK 0xff
+#define MSG_TYPE_SHIFT 8
+#define MSG_TYPE_MASK 0x3
+#define MSG_PROTOCOL_ID_SHIFT 10
+#define MSG_PROTOCOL_ID_MASK 0xff
+#define MSG_TOKEN_ID_SHIFT 18
+#define MSG_TOKEN_ID_MASK 0x3ff
+#define MSG_XTRACT_TOKEN(header) \
+ (((header) >> MSG_TOKEN_ID_SHIFT) & MSG_TOKEN_ID_MASK)
+
+enum scmi_error_codes {
+ SCMI_SUCCESS = 0, /* Success */
+ SCMI_ERR_SUPPORT = -1, /* Not supported */
+ SCMI_ERR_PARAMS = -2, /* Invalid Parameters */
+ SCMI_ERR_ACCESS = -3, /* Invalid access/permission denied */
+ SCMI_ERR_ENTRY = -4, /* Not found */
+ SCMI_ERR_RANGE = -5, /* Value out of range */
+ SCMI_ERR_BUSY = -6, /* Device busy */
+ SCMI_ERR_COMMS = -7, /* Communication Error */
+ SCMI_ERR_GENERIC = -8, /* Generic Error */
+ SCMI_ERR_HARDWARE = -9, /* Hardware Error */
+ SCMI_ERR_PROTOCOL = -10,/* Protocol Error */
+ SCMI_ERR_MAX
+};
+
+/* List of all SCMI devices active in system */
+static LIST_HEAD(scmi_list);
+/* Protection for the entire list */
+static DEFINE_MUTEX(scmi_list_mutex);
+
+/**
+ * struct scmi_xfers_info - Structure to manage transfer information
+ *
+ * @xfer_block: Preallocated Message array
+ * @xfer_alloc_table: Bitmap table for allocated messages.
+ * Index of this bitmap table is also used for message
+ * sequence identifier.
+ * @xfer_lock: Protection for message allocation
+ */
+struct scmi_xfers_info {
+ struct scmi_xfer *xfer_block;
+ unsigned long *xfer_alloc_table;
+ /* protect transfer allocation */
+ spinlock_t xfer_lock;
+};
+
+/**
+ * struct scmi_desc - Description of SoC integration
+ *
+ * @max_rx_timeout_ms: Timeout for communication with SoC (in Milliseconds)
+ * @max_msg: Maximum number of messages that can be pending
+ * simultaneously in the system
+ * @max_msg_size: Maximum size of data per message that can be handled.
+ */
+struct scmi_desc {
+ int max_rx_timeout_ms;
+ int max_msg;
+ int max_msg_size;
+};
+
+/**
+ * struct scmi_chan_info - Structure representing a SCMI channel informfation
+ *
+ * @cl: Mailbox Client
+ * @chan: Transmit/Receive mailbox channel
+ * @payload: Transmit/Receive mailbox channel payload area
+ * @dev: Reference to device in the SCMI hierarchy corresponding to this
+ * channel
+ */
+struct scmi_chan_info {
+ struct mbox_client cl;
+ struct mbox_chan *chan;
+ void __iomem *payload;
+ struct device *dev;
+ struct scmi_handle *handle;
+};
+
+/**
+ * struct scmi_info - Structure representing a SCMI instance
+ *
+ * @dev: Device pointer
+ * @desc: SoC description for this instance
+ * @handle: Instance of SCMI handle to send to clients
+ * @version: SCMI revision information containing protocol version,
+ * implementation version and (sub-)vendor identification.
+ * @minfo: Message info
+ * @tx_idr: IDR object to map protocol id to channel info pointer
+ * @protocols_imp: list of protocols implemented, currently maximum of
+ * MAX_PROTOCOLS_IMP elements allocated by the base protocol
+ * @node: list head
+ * @users: Number of users of this instance
+ */
+struct scmi_info {
+ struct device *dev;
+ const struct scmi_desc *desc;
+ struct scmi_revision_info version;
+ struct scmi_handle handle;
+ struct scmi_xfers_info minfo;
+ struct idr tx_idr;
+ u8 *protocols_imp;
+ struct list_head node;
+ int users;
+};
+
+#define client_to_scmi_chan_info(c) container_of(c, struct scmi_chan_info, cl)
+#define handle_to_scmi_info(h) container_of(h, struct scmi_info, handle)
+
+/*
+ * SCMI specification requires all parameters, message headers, return
+ * arguments or any protocol data to be expressed in little endian
+ * format only.
+ */
+struct scmi_shared_mem {
+ __le32 reserved;
+ __le32 channel_status;
+#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR BIT(1)
+#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE BIT(0)
+ __le32 reserved1[2];
+ __le32 flags;
+#define SCMI_SHMEM_FLAG_INTR_ENABLED BIT(0)
+ __le32 length;
+ __le32 msg_header;
+ u8 msg_payload[0];
+};
+
+static const int scmi_linux_errmap[] = {
+ /* better than switch case as long as return value is continuous */
+ 0, /* SCMI_SUCCESS */
+ -EOPNOTSUPP, /* SCMI_ERR_SUPPORT */
+ -EINVAL, /* SCMI_ERR_PARAM */
+ -EACCES, /* SCMI_ERR_ACCESS */
+ -ENOENT, /* SCMI_ERR_ENTRY */
+ -ERANGE, /* SCMI_ERR_RANGE */
+ -EBUSY, /* SCMI_ERR_BUSY */
+ -ECOMM, /* SCMI_ERR_COMMS */
+ -EIO, /* SCMI_ERR_GENERIC */
+ -EREMOTEIO, /* SCMI_ERR_HARDWARE */
+ -EPROTO, /* SCMI_ERR_PROTOCOL */
+};
+
+static inline int scmi_to_linux_errno(int errno)
+{
+ if (errno < SCMI_SUCCESS && errno > SCMI_ERR_MAX)
+ return scmi_linux_errmap[-errno];
+ return -EIO;
+}
+
+/**
+ * scmi_dump_header_dbg() - Helper to dump a message header.
+ *
+ * @dev: Device pointer corresponding to the SCMI entity
+ * @hdr: pointer to header.
+ */
+static inline void scmi_dump_header_dbg(struct device *dev,
+ struct scmi_msg_hdr *hdr)
+{
+ dev_dbg(dev, "Command ID: %x Sequence ID: %x Protocol: %x\n",
+ hdr->id, hdr->seq, hdr->protocol_id);
+}
+
+static void scmi_fetch_response(struct scmi_xfer *xfer,
+ struct scmi_shared_mem __iomem *mem)
+{
+ xfer->hdr.status = ioread32(mem->msg_payload);
+ /* Skip the length of header and statues in payload area i.e 8 bytes*/
+ xfer->rx.len = min_t(size_t, xfer->rx.len, ioread32(&mem->length) - 8);
+
+ /* Take a copy to the rx buffer.. */
+ memcpy_fromio(xfer->rx.buf, mem->msg_payload + 4, xfer->rx.len);
+}
+
+/**
+ * scmi_rx_callback() - mailbox client callback for receive messages
+ *
+ * @cl: client pointer
+ * @m: mailbox message
+ *
+ * Processes one received message to appropriate transfer information and
+ * signals completion of the transfer.
+ *
+ * NOTE: This function will be invoked in IRQ context, hence should be
+ * as optimal as possible.
+ */
+static void scmi_rx_callback(struct mbox_client *cl, void *m)
+{
+ u16 xfer_id;
+ struct scmi_xfer *xfer;
+ struct scmi_chan_info *cinfo = client_to_scmi_chan_info(cl);
+ struct device *dev = cinfo->dev;
+ struct scmi_info *info = handle_to_scmi_info(cinfo->handle);
+ struct scmi_xfers_info *minfo = &info->minfo;
+ struct scmi_shared_mem __iomem *mem = cinfo->payload;
+
+ xfer_id = MSG_XTRACT_TOKEN(ioread32(&mem->msg_header));
+
+ /*
+ * Are we even expecting this?
+ */
+ if (!test_bit(xfer_id, minfo->xfer_alloc_table)) {
+ dev_err(dev, "message for %d is not expected!\n", xfer_id);
+ return;
+ }
+
+ xfer = &minfo->xfer_block[xfer_id];
+
+ scmi_dump_header_dbg(dev, &xfer->hdr);
+ /* Is the message of valid length? */
+ if (xfer->rx.len > info->desc->max_msg_size) {
+ dev_err(dev, "unable to handle %zu xfer(max %d)\n",
+ xfer->rx.len, info->desc->max_msg_size);
+ return;
+ }
+
+ scmi_fetch_response(xfer, mem);
+ complete(&xfer->done);
+}
+
+/**
+ * pack_scmi_header() - packs and returns 32-bit header
+ *
+ * @hdr: pointer to header containing all the information on message id,
+ * protocol id and sequence id.
+ */
+static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr)
+{
+ return ((hdr->id & MSG_ID_MASK) << MSG_ID_SHIFT) |
+ ((hdr->seq & MSG_TOKEN_ID_MASK) << MSG_TOKEN_ID_SHIFT) |
+ ((hdr->protocol_id & MSG_PROTOCOL_ID_MASK) << MSG_PROTOCOL_ID_SHIFT);
+}
+
+/**
+ * scmi_tx_prepare() - mailbox client callback to prepare for the transfer
+ *
+ * @cl: client pointer
+ * @m: mailbox message
+ *
+ * This function prepares the shared memory which contains the header and the
+ * payload.
+ */
+static void scmi_tx_prepare(struct mbox_client *cl, void *m)
+{
+ struct scmi_xfer *t = m;
+ struct scmi_chan_info *cinfo = client_to_scmi_chan_info(cl);
+ struct scmi_shared_mem __iomem *mem = cinfo->payload;
+
+ /* Mark channel busy + clear error */
+ iowrite32(0x0, &mem->channel_status);
+ iowrite32(t->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED,
+ &mem->flags);
+ iowrite32(sizeof(mem->msg_header) + t->tx.len, &mem->length);
+ iowrite32(pack_scmi_header(&t->hdr), &mem->msg_header);
+ if (t->tx.buf)
+ memcpy_toio(mem->msg_payload, t->tx.buf, t->tx.len);
+}
+
+/**
+ * scmi_one_xfer_get() - Allocate one message
+ *
+ * @handle: SCMI entity handle
+ *
+ * Helper function which is used by various command functions that are
+ * exposed to clients of this driver for allocating a message traffic event.
+ *
+ * This function can sleep depending on pending requests already in the system
+ * for the SCMI entity. Further, this also holds a spinlock to maintain
+ * integrity of internal data structures.
+ *
+ * Return: 0 if all went fine, else corresponding error.
+ */
+static struct scmi_xfer *scmi_one_xfer_get(const struct scmi_handle *handle)
+{
+ u16 xfer_id;
+ struct scmi_xfer *xfer;
+ unsigned long flags, bit_pos;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct scmi_xfers_info *minfo = &info->minfo;
+
+ /* Keep the locked section as small as possible */
+ spin_lock_irqsave(&minfo->xfer_lock, flags);
+ bit_pos = find_first_zero_bit(minfo->xfer_alloc_table,
+ info->desc->max_msg);
+ if (bit_pos == info->desc->max_msg) {
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+ return ERR_PTR(-ENOMEM);
+ }
+ set_bit(bit_pos, minfo->xfer_alloc_table);
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+
+ xfer_id = bit_pos;
+
+ xfer = &minfo->xfer_block[xfer_id];
+ xfer->hdr.seq = xfer_id;
+ reinit_completion(&xfer->done);
+
+ return xfer;
+}
+
+/**
+ * scmi_one_xfer_put() - Release a message
+ *
+ * @minfo: transfer info pointer
+ * @xfer: message that was reserved by scmi_one_xfer_get
+ *
+ * This holds a spinlock to maintain integrity of internal data structures.
+ */
+void scmi_one_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer)
+{
+ unsigned long flags;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct scmi_xfers_info *minfo = &info->minfo;
+
+ /*
+ * Keep the locked section as small as possible
+ * NOTE: we might escape with smp_mb and no lock here..
+ * but just be conservative and symmetric.
+ */
+ spin_lock_irqsave(&minfo->xfer_lock, flags);
+ clear_bit(xfer->hdr.seq, minfo->xfer_alloc_table);
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+}
+
+static bool
+scmi_xfer_poll_done(const struct scmi_chan_info *cinfo, struct scmi_xfer *xfer)
+{
+ struct scmi_shared_mem __iomem *mem = cinfo->payload;
+ u16 xfer_id = MSG_XTRACT_TOKEN(ioread32(&mem->msg_header));
+
+ if (xfer->hdr.seq != xfer_id)
+ return false;
+
+ return ioread32(&mem->channel_status) &
+ (SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR |
+ SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE);
+}
+
+#define SCMI_MAX_POLL_TO_NS (100 * NSEC_PER_USEC)
+
+static bool scmi_xfer_done_no_timeout(const struct scmi_chan_info *cinfo,
+ struct scmi_xfer *xfer, ktime_t stop)
+{
+ ktime_t __cur = ktime_get();
+
+ return scmi_xfer_poll_done(cinfo, xfer) || ktime_after(__cur, stop);
+}
+
+/**
+ * scmi_do_xfer() - Do one transfer
+ *
+ * @info: Pointer to SCMI entity information
+ * @xfer: Transfer to initiate and wait for response
+ *
+ * Return: -ETIMEDOUT in case of no response, if transmit error,
+ * return corresponding error, else if all goes well,
+ * return 0.
+ */
+int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
+{
+ int ret;
+ int timeout;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct device *dev = info->dev;
+ struct scmi_chan_info *cinfo;
+
+ cinfo = idr_find(&info->tx_idr, xfer->hdr.protocol_id);
+ if (unlikely(!cinfo))
+ return -EINVAL;
+
+ ret = mbox_send_message(cinfo->chan, xfer);
+ if (ret < 0) {
+ dev_dbg(dev, "mbox send fail %d\n", ret);
+ return ret;
+ }
+
+ /* mbox_send_message returns non-negative value on success, so reset */
+ ret = 0;
+
+ if (xfer->hdr.poll_completion) {
+ ktime_t stop = ktime_add_ns(ktime_get(), SCMI_MAX_POLL_TO_NS);
+
+ spin_until_cond(scmi_xfer_done_no_timeout(cinfo, xfer, stop));
+
+ if (ktime_before(ktime_get(), stop))
+ scmi_fetch_response(xfer, cinfo->payload);
+ else
+ ret = -ETIMEDOUT;
+ } else {
+ /* And we wait for the response. */
+ timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms);
+ if (!wait_for_completion_timeout(&xfer->done, timeout)) {
+ dev_err(dev, "mbox timed out in resp(caller: %pS)\n",
+ (void *)_RET_IP_);
+ ret = -ETIMEDOUT;
+ }
+ }
+
+ if (!ret && xfer->hdr.status)
+ ret = scmi_to_linux_errno(xfer->hdr.status);
+
+ /*
+ * NOTE: we might prefer not to need the mailbox ticker to manage the
+ * transfer queueing since the protocol layer queues things by itself.
+ * Unfortunately, we have to kick the mailbox framework after we have
+ * received our message.
+ */
+ mbox_client_txdone(cinfo->chan, ret);
+
+ return ret;
+}
+
+/**
+ * scmi_one_xfer_init() - Allocate and initialise one message
+ *
+ * @handle: SCMI entity handle
+ * @msg_id: Message identifier
+ * @msg_prot_id: Protocol identifier for the message
+ * @tx_size: transmit message size
+ * @rx_size: receive message size
+ * @p: pointer to the allocated and initialised message
+ *
+ * This function allocates the message using @scmi_one_xfer_get and
+ * initialise the header.
+ *
+ * Return: 0 if all went fine with @p pointing to message, else
+ * corresponding error.
+ */
+int scmi_one_xfer_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id,
+ size_t tx_size, size_t rx_size, struct scmi_xfer **p)
+{
+ int ret;
+ struct scmi_xfer *xfer;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct device *dev = info->dev;
+
+ /* Ensure we have sane transfer sizes */
+ if (rx_size > info->desc->max_msg_size ||
+ tx_size > info->desc->max_msg_size)
+ return -ERANGE;
+
+ xfer = scmi_one_xfer_get(handle);
+ if (IS_ERR(xfer)) {
+ ret = PTR_ERR(xfer);
+ dev_err(dev, "failed to get free message slot(%d)\n", ret);
+ return ret;
+ }
+
+ xfer->tx.len = tx_size;
+ xfer->rx.len = rx_size ? : info->desc->max_msg_size;
+ xfer->hdr.id = msg_id;
+ xfer->hdr.protocol_id = prot_id;
+ xfer->hdr.poll_completion = false;
+
+ *p = xfer;
+ return 0;
+}
+
+/**
+ * scmi_version_get() - command to get the revision of the SCMI entity
+ *
+ * @handle: Handle to SCMI entity information
+ *
+ * Updates the SCMI information in the internal data structure.
+ *
+ * Return: 0 if all went fine, else return appropriate error.
+ */
+int scmi_version_get(const struct scmi_handle *handle, u8 protocol,
+ u32 *version)
+{
+ int ret;
+ __le32 *rev_info;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_VERSION, protocol, 0,
+ sizeof(*version), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ rev_info = t->rx.buf;
+ *version = le32_to_cpu(*rev_info);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
+ u8 *prot_imp)
+{
+ struct scmi_info *info = handle_to_scmi_info(handle);
+
+ info->protocols_imp = prot_imp;
+}
+
+static bool
+scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id)
+{
+ int i;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+
+ if (!info->protocols_imp)
+ return false;
+
+ for (i = 0; i < MAX_PROTOCOLS_IMP; i++)
+ if (info->protocols_imp[i] == prot_id)
+ return true;
+ return false;
+}
+
+/**
+ * scmi_handle_get() - Get the SCMI handle for a device
+ *
+ * @dev: pointer to device for which we want SCMI handle
+ *
+ * NOTE: The function does not track individual clients of the framework
+ * and is expected to be maintained by caller of SCMI protocol library.
+ * scmi_handle_put must be balanced with successful scmi_handle_get
+ *
+ * Return: pointer to handle if successful, NULL on error
+ */
+struct scmi_handle *scmi_handle_get(struct device *dev)
+{
+ struct list_head *p;
+ struct scmi_info *info;
+ struct scmi_handle *handle = NULL;
+
+ mutex_lock(&scmi_list_mutex);
+ list_for_each(p, &scmi_list) {
+ info = list_entry(p, struct scmi_info, node);
+ if (dev->parent == info->dev) {
+ handle = &info->handle;
+ info->users++;
+ break;
+ }
+ }
+ mutex_unlock(&scmi_list_mutex);
+
+ return handle;
+}
+
+/**
+ * scmi_handle_put() - Release the handle acquired by scmi_handle_get
+ *
+ * @handle: handle acquired by scmi_handle_get
+ *
+ * NOTE: The function does not track individual clients of the framework
+ * and is expected to be maintained by caller of SCMI protocol library.
+ * scmi_handle_put must be balanced with successful scmi_handle_get
+ *
+ * Return: 0 is successfully released
+ * if null was passed, it returns -EINVAL;
+ */
+int scmi_handle_put(const struct scmi_handle *handle)
+{
+ struct scmi_info *info;
+
+ if (!handle)
+ return -EINVAL;
+
+ info = handle_to_scmi_info(handle);
+ mutex_lock(&scmi_list_mutex);
+ if (!WARN_ON(!info->users))
+ info->users--;
+ mutex_unlock(&scmi_list_mutex);
+
+ return 0;
+}
+
+static const struct scmi_desc scmi_generic_desc = {
+ .max_rx_timeout_ms = 30, /* we may increase this if required */
+ .max_msg = 20, /* Limited by MBOX_TX_QUEUE_LEN */
+ .max_msg_size = 128,
+};
+
+/* Each compatible listed below must have descriptor associated with it */
+static const struct of_device_id scmi_of_match[] = {
+ { .compatible = "arm,scmi", .data = &scmi_generic_desc },
+ { /* Sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, scmi_of_match);
+
+static int scmi_xfer_info_init(struct scmi_info *sinfo)
+{
+ int i;
+ struct scmi_xfer *xfer;
+ struct device *dev = sinfo->dev;
+ const struct scmi_desc *desc = sinfo->desc;
+ struct scmi_xfers_info *info = &sinfo->minfo;
+
+ /* Pre-allocated messages, no more than what hdr.seq can support */
+ if (WARN_ON(desc->max_msg >= (MSG_TOKEN_ID_MASK + 1))) {
+ dev_err(dev, "Maximum message of %d exceeds supported %d\n",
+ desc->max_msg, MSG_TOKEN_ID_MASK + 1);
+ return -EINVAL;
+ }
+
+ info->xfer_block = devm_kcalloc(dev, desc->max_msg,
+ sizeof(*info->xfer_block), GFP_KERNEL);
+ if (!info->xfer_block)
+ return -ENOMEM;
+
+ info->xfer_alloc_table = devm_kcalloc(dev, BITS_TO_LONGS(desc->max_msg),
+ sizeof(long), GFP_KERNEL);
+ if (!info->xfer_alloc_table)
+ return -ENOMEM;
+
+ bitmap_zero(info->xfer_alloc_table, desc->max_msg);
+
+ /* Pre-initialize the buffer pointer to pre-allocated buffers */
+ for (i = 0, xfer = info->xfer_block; i < desc->max_msg; i++, xfer++) {
+ xfer->rx.buf = devm_kcalloc(dev, sizeof(u8), desc->max_msg_size,
+ GFP_KERNEL);
+ if (!xfer->rx.buf)
+ return -ENOMEM;
+
+ xfer->tx.buf = xfer->rx.buf;
+ init_completion(&xfer->done);
+ }
+
+ spin_lock_init(&info->xfer_lock);
+
+ return 0;
+}
+
+static int scmi_mailbox_check(struct device_node *np)
+{
+ struct of_phandle_args arg;
+
+ return of_parse_phandle_with_args(np, "mboxes", "#mbox-cells", 0, &arg);
+}
+
+static int scmi_mbox_free_channel(int id, void *p, void *data)
+{
+ struct scmi_chan_info *cinfo = p;
+ struct idr *idr = data;
+
+ if (!IS_ERR_OR_NULL(cinfo->chan)) {
+ mbox_free_channel(cinfo->chan);
+ cinfo->chan = NULL;
+ }
+
+ idr_remove(idr, id);
+
+ return 0;
+}
+
+static int scmi_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct scmi_info *info = platform_get_drvdata(pdev);
+ struct idr *idr = &info->tx_idr;
+
+ mutex_lock(&scmi_list_mutex);
+ if (info->users)
+ ret = -EBUSY;
+ else
+ list_del(&info->node);
+ mutex_unlock(&scmi_list_mutex);
+
+ if (!ret) {
+ /* Safe to free channels since no more users */
+ ret = idr_for_each(idr, scmi_mbox_free_channel, idr);
+ idr_destroy(&info->tx_idr);
+ }
+
+ return ret;
+}
+
+static inline int
+scmi_mbox_chan_setup(struct scmi_info *info, struct device *dev, int prot_id)
+{
+ int ret;
+ struct resource res;
+ resource_size_t size;
+ struct device_node *shmem, *np = dev->of_node;
+ struct scmi_chan_info *cinfo;
+ struct mbox_client *cl;
+
+ if (scmi_mailbox_check(np)) {
+ cinfo = idr_find(&info->tx_idr, SCMI_PROTOCOL_BASE);
+ goto idr_alloc;
+ }
+
+ cinfo = devm_kzalloc(info->dev, sizeof(*cinfo), GFP_KERNEL);
+ if (!cinfo)
+ return -ENOMEM;
+
+ cinfo->dev = dev;
+
+ cl = &cinfo->cl;
+ cl->dev = dev;
+ cl->rx_callback = scmi_rx_callback;
+ cl->tx_prepare = scmi_tx_prepare;
+ cl->tx_block = false;
+ cl->knows_txdone = true;
+
+ shmem = of_parse_phandle(np, "shmem", 0);
+ ret = of_address_to_resource(shmem, 0, &res);
+ of_node_put(shmem);
+ if (ret) {
+ dev_err(dev, "failed to get SCMI Tx payload mem resource\n");
+ return ret;
+ }
+
+ size = resource_size(&res);
+ cinfo->payload = devm_ioremap(info->dev, res.start, size);
+ if (!cinfo->payload) {
+ dev_err(dev, "failed to ioremap SCMI Tx payload\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ /* Transmit channel is first entry i.e. index 0 */
+ cinfo->chan = mbox_request_channel(cl, 0);
+ if (IS_ERR(cinfo->chan)) {
+ ret = PTR_ERR(cinfo->chan);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to request SCMI Tx mailbox\n");
+ return ret;
+ }
+
+idr_alloc:
+ ret = idr_alloc(&info->tx_idr, cinfo, prot_id, prot_id + 1, GFP_KERNEL);
+ if (ret != prot_id) {
+ dev_err(dev, "unable to allocate SCMI idr slot err %d\n", ret);
+ return ret;
+ }
+
+ cinfo->handle = &info->handle;
+ return 0;
+}
+
+static inline void
+scmi_create_protocol_device(struct device_node *np, struct scmi_info *info,
+ int prot_id)
+{
+ struct scmi_device *sdev;
+
+ sdev = scmi_device_create(np, info->dev, prot_id);
+ if (!sdev) {
+ dev_err(info->dev, "failed to create %d protocol device\n",
+ prot_id);
+ return;
+ }
+
+ if (scmi_mbox_chan_setup(info, &sdev->dev, prot_id)) {
+ dev_err(&sdev->dev, "failed to setup transport\n");
+ scmi_device_destroy(sdev);
+ }
+
+ /* setup handle now as the transport is ready */
+ scmi_set_handle(sdev);
+}
+
+static int scmi_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct scmi_handle *handle;
+ const struct scmi_desc *desc;
+ struct scmi_info *info;
+ struct device *dev = &pdev->dev;
+ struct device_node *child, *np = dev->of_node;
+
+ /* Only mailbox method supported, check for the presence of one */
+ if (scmi_mailbox_check(np)) {
+ dev_err(dev, "no mailbox found in %pOF\n", np);
+ return -EINVAL;
+ }
+
+ desc = of_match_device(scmi_of_match, dev)->data;
+
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = dev;
+ info->desc = desc;
+ INIT_LIST_HEAD(&info->node);
+
+ ret = scmi_xfer_info_init(info);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, info);
+ idr_init(&info->tx_idr);
+
+ handle = &info->handle;
+ handle->dev = info->dev;
+ handle->version = &info->version;
+
+ ret = scmi_mbox_chan_setup(info, dev, SCMI_PROTOCOL_BASE);
+ if (ret)
+ return ret;
+
+ ret = scmi_base_protocol_init(handle);
+ if (ret) {
+ dev_err(dev, "unable to communicate with SCMI(%d)\n", ret);
+ return ret;
+ }
+
+ mutex_lock(&scmi_list_mutex);
+ list_add_tail(&info->node, &scmi_list);
+ mutex_unlock(&scmi_list_mutex);
+
+ for_each_available_child_of_node(np, child) {
+ u32 prot_id;
+
+ if (of_property_read_u32(child, "reg", &prot_id))
+ continue;
+
+ prot_id &= MSG_PROTOCOL_ID_MASK;
+
+ if (!scmi_is_protocol_implemented(handle, prot_id)) {
+ dev_err(dev, "SCMI protocol %d not implemented\n",
+ prot_id);
+ continue;
+ }
+
+ scmi_create_protocol_device(child, info, prot_id);
+ }
+
+ return 0;
+}
+
+static struct platform_driver scmi_driver = {
+ .driver = {
+ .name = "arm-scmi",
+ .of_match_table = scmi_of_match,
+ },
+ .probe = scmi_probe,
+ .remove = scmi_remove,
+};
+
+module_platform_driver(scmi_driver);
+
+MODULE_ALIAS("platform: arm-scmi");
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI protocol driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
new file mode 100644
index 000000000000..987c64d19801
--- /dev/null
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Performance Protocol
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/sort.h>
+
+#include "common.h"
+
+enum scmi_performance_protocol_cmd {
+ PERF_DOMAIN_ATTRIBUTES = 0x3,
+ PERF_DESCRIBE_LEVELS = 0x4,
+ PERF_LIMITS_SET = 0x5,
+ PERF_LIMITS_GET = 0x6,
+ PERF_LEVEL_SET = 0x7,
+ PERF_LEVEL_GET = 0x8,
+ PERF_NOTIFY_LIMITS = 0x9,
+ PERF_NOTIFY_LEVEL = 0xa,
+};
+
+struct scmi_opp {
+ u32 perf;
+ u32 power;
+ u32 trans_latency_us;
+};
+
+struct scmi_msg_resp_perf_attributes {
+ __le16 num_domains;
+ __le16 flags;
+#define POWER_SCALE_IN_MILLIWATT(x) ((x) & BIT(0))
+ __le32 stats_addr_low;
+ __le32 stats_addr_high;
+ __le32 stats_size;
+};
+
+struct scmi_msg_resp_perf_domain_attributes {
+ __le32 flags;
+#define SUPPORTS_SET_LIMITS(x) ((x) & BIT(31))
+#define SUPPORTS_SET_PERF_LVL(x) ((x) & BIT(30))
+#define SUPPORTS_PERF_LIMIT_NOTIFY(x) ((x) & BIT(29))
+#define SUPPORTS_PERF_LEVEL_NOTIFY(x) ((x) & BIT(28))
+ __le32 rate_limit_us;
+ __le32 sustained_freq_khz;
+ __le32 sustained_perf_level;
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_msg_perf_describe_levels {
+ __le32 domain;
+ __le32 level_index;
+};
+
+struct scmi_perf_set_limits {
+ __le32 domain;
+ __le32 max_level;
+ __le32 min_level;
+};
+
+struct scmi_perf_get_limits {
+ __le32 max_level;
+ __le32 min_level;
+};
+
+struct scmi_perf_set_level {
+ __le32 domain;
+ __le32 level;
+};
+
+struct scmi_perf_notify_level_or_limits {
+ __le32 domain;
+ __le32 notify_enable;
+};
+
+struct scmi_msg_resp_perf_describe_levels {
+ __le16 num_returned;
+ __le16 num_remaining;
+ struct {
+ __le32 perf_val;
+ __le32 power;
+ __le16 transition_latency_us;
+ __le16 reserved;
+ } opp[0];
+};
+
+struct perf_dom_info {
+ bool set_limits;
+ bool set_perf;
+ bool perf_limit_notify;
+ bool perf_level_notify;
+ u32 opp_count;
+ u32 sustained_freq_khz;
+ u32 sustained_perf_level;
+ u32 mult_factor;
+ char name[SCMI_MAX_STR_SIZE];
+ struct scmi_opp opp[MAX_OPPS];
+};
+
+struct scmi_perf_info {
+ int num_domains;
+ bool power_scale_mw;
+ u64 stats_addr;
+ u32 stats_size;
+ struct perf_dom_info *dom_info;
+};
+
+static int scmi_perf_attributes_get(const struct scmi_handle *handle,
+ struct scmi_perf_info *pi)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_perf_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_PERF, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u16 flags = le16_to_cpu(attr->flags);
+
+ pi->num_domains = le16_to_cpu(attr->num_domains);
+ pi->power_scale_mw = POWER_SCALE_IN_MILLIWATT(flags);
+ pi->stats_addr = le32_to_cpu(attr->stats_addr_low) |
+ (u64)le32_to_cpu(attr->stats_addr_high) << 32;
+ pi->stats_size = le32_to_cpu(attr->stats_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_perf_domain_attributes_get(const struct scmi_handle *handle, u32 domain,
+ struct perf_dom_info *dom_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_perf_domain_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PERF_DOMAIN_ATTRIBUTES,
+ SCMI_PROTOCOL_PERF, sizeof(domain),
+ sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u32 flags = le32_to_cpu(attr->flags);
+
+ dom_info->set_limits = SUPPORTS_SET_LIMITS(flags);
+ dom_info->set_perf = SUPPORTS_SET_PERF_LVL(flags);
+ dom_info->perf_limit_notify = SUPPORTS_PERF_LIMIT_NOTIFY(flags);
+ dom_info->perf_level_notify = SUPPORTS_PERF_LEVEL_NOTIFY(flags);
+ dom_info->sustained_freq_khz =
+ le32_to_cpu(attr->sustained_freq_khz);
+ dom_info->sustained_perf_level =
+ le32_to_cpu(attr->sustained_perf_level);
+ dom_info->mult_factor = (dom_info->sustained_freq_khz * 1000) /
+ dom_info->sustained_perf_level;
+ memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int opp_cmp_func(const void *opp1, const void *opp2)
+{
+ const struct scmi_opp *t1 = opp1, *t2 = opp2;
+
+ return t1->perf - t2->perf;
+}
+
+static int
+scmi_perf_describe_levels_get(const struct scmi_handle *handle, u32 domain,
+ struct perf_dom_info *perf_dom)
+{
+ int ret, cnt;
+ u32 tot_opp_cnt = 0;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_opp *opp;
+ struct scmi_msg_perf_describe_levels *dom_info;
+ struct scmi_msg_resp_perf_describe_levels *level_info;
+
+ ret = scmi_one_xfer_init(handle, PERF_DESCRIBE_LEVELS,
+ SCMI_PROTOCOL_PERF, sizeof(*dom_info), 0, &t);
+ if (ret)
+ return ret;
+
+ dom_info = t->tx.buf;
+ level_info = t->rx.buf;
+
+ do {
+ dom_info->domain = cpu_to_le32(domain);
+ /* Set the number of OPPs to be skipped/already read */
+ dom_info->level_index = cpu_to_le32(tot_opp_cnt);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ num_returned = le16_to_cpu(level_info->num_returned);
+ num_remaining = le16_to_cpu(level_info->num_remaining);
+ if (tot_opp_cnt + num_returned > MAX_OPPS) {
+ dev_err(handle->dev, "No. of OPPs exceeded MAX_OPPS");
+ break;
+ }
+
+ opp = &perf_dom->opp[tot_opp_cnt];
+ for (cnt = 0; cnt < num_returned; cnt++, opp++) {
+ opp->perf = le32_to_cpu(level_info->opp[cnt].perf_val);
+ opp->power = le32_to_cpu(level_info->opp[cnt].power);
+ opp->trans_latency_us = le16_to_cpu
+ (level_info->opp[cnt].transition_latency_us);
+
+ dev_dbg(handle->dev, "Level %d Power %d Latency %dus\n",
+ opp->perf, opp->power, opp->trans_latency_us);
+ }
+
+ tot_opp_cnt += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ perf_dom->opp_count = tot_opp_cnt;
+ scmi_one_xfer_put(handle, t);
+
+ sort(perf_dom->opp, tot_opp_cnt, sizeof(*opp), opp_cmp_func, NULL);
+ return ret;
+}
+
+static int scmi_perf_limits_set(const struct scmi_handle *handle, u32 domain,
+ u32 max_perf, u32 min_perf)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_set_limits *limits;
+
+ ret = scmi_one_xfer_init(handle, PERF_LIMITS_SET, SCMI_PROTOCOL_PERF,
+ sizeof(*limits), 0, &t);
+ if (ret)
+ return ret;
+
+ limits = t->tx.buf;
+ limits->domain = cpu_to_le32(domain);
+ limits->max_level = cpu_to_le32(max_perf);
+ limits->min_level = cpu_to_le32(min_perf);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_perf_limits_get(const struct scmi_handle *handle, u32 domain,
+ u32 *max_perf, u32 *min_perf)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_get_limits *limits;
+
+ ret = scmi_one_xfer_init(handle, PERF_LIMITS_GET, SCMI_PROTOCOL_PERF,
+ sizeof(__le32), 0, &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ limits = t->rx.buf;
+
+ *max_perf = le32_to_cpu(limits->max_level);
+ *min_perf = le32_to_cpu(limits->min_level);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_perf_level_set(const struct scmi_handle *handle, u32 domain,
+ u32 level, bool poll)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_set_level *lvl;
+
+ ret = scmi_one_xfer_init(handle, PERF_LEVEL_SET, SCMI_PROTOCOL_PERF,
+ sizeof(*lvl), 0, &t);
+ if (ret)
+ return ret;
+
+ t->hdr.poll_completion = poll;
+ lvl = t->tx.buf;
+ lvl->domain = cpu_to_le32(domain);
+ lvl->level = cpu_to_le32(level);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_perf_level_get(const struct scmi_handle *handle, u32 domain,
+ u32 *level, bool poll)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, PERF_LEVEL_GET, SCMI_PROTOCOL_PERF,
+ sizeof(u32), sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ t->hdr.poll_completion = poll;
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ *level = le32_to_cpu(*(__le32 *)t->rx.buf);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/* Device specific ops */
+static int scmi_dev_domain_id(struct device *dev)
+{
+ struct of_phandle_args clkspec;
+
+ if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells",
+ 0, &clkspec))
+ return -EINVAL;
+
+ return clkspec.args[0];
+}
+
+static int scmi_dvfs_add_opps_to_device(const struct scmi_handle *handle,
+ struct device *dev)
+{
+ int idx, ret, domain;
+ unsigned long freq;
+ struct scmi_opp *opp;
+ struct perf_dom_info *dom;
+ struct scmi_perf_info *pi = handle->perf_priv;
+
+ domain = scmi_dev_domain_id(dev);
+ if (domain < 0)
+ return domain;
+
+ dom = pi->dom_info + domain;
+ if (!dom)
+ return -EIO;
+
+ for (opp = dom->opp, idx = 0; idx < dom->opp_count; idx++, opp++) {
+ freq = opp->perf * dom->mult_factor;
+
+ ret = dev_pm_opp_add(dev, freq, 0);
+ if (ret) {
+ dev_warn(dev, "failed to add opp %luHz\n", freq);
+
+ while (idx-- > 0) {
+ freq = (--opp)->perf * dom->mult_factor;
+ dev_pm_opp_remove(dev, freq);
+ }
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int scmi_dvfs_get_transition_latency(const struct scmi_handle *handle,
+ struct device *dev)
+{
+ struct perf_dom_info *dom;
+ struct scmi_perf_info *pi = handle->perf_priv;
+ int domain = scmi_dev_domain_id(dev);
+
+ if (domain < 0)
+ return domain;
+
+ dom = pi->dom_info + domain;
+ if (!dom)
+ return -EIO;
+
+ /* uS to nS */
+ return dom->opp[dom->opp_count - 1].trans_latency_us * 1000;
+}
+
+static int scmi_dvfs_freq_set(const struct scmi_handle *handle, u32 domain,
+ unsigned long freq, bool poll)
+{
+ struct scmi_perf_info *pi = handle->perf_priv;
+ struct perf_dom_info *dom = pi->dom_info + domain;
+
+ return scmi_perf_level_set(handle, domain, freq / dom->mult_factor,
+ poll);
+}
+
+static int scmi_dvfs_freq_get(const struct scmi_handle *handle, u32 domain,
+ unsigned long *freq, bool poll)
+{
+ int ret;
+ u32 level;
+ struct scmi_perf_info *pi = handle->perf_priv;
+ struct perf_dom_info *dom = pi->dom_info + domain;
+
+ ret = scmi_perf_level_get(handle, domain, &level, poll);
+ if (!ret)
+ *freq = level * dom->mult_factor;
+
+ return ret;
+}
+
+static struct scmi_perf_ops perf_ops = {
+ .limits_set = scmi_perf_limits_set,
+ .limits_get = scmi_perf_limits_get,
+ .level_set = scmi_perf_level_set,
+ .level_get = scmi_perf_level_get,
+ .device_domain_id = scmi_dev_domain_id,
+ .get_transition_latency = scmi_dvfs_get_transition_latency,
+ .add_opps_to_device = scmi_dvfs_add_opps_to_device,
+ .freq_set = scmi_dvfs_freq_set,
+ .freq_get = scmi_dvfs_freq_get,
+};
+
+static int scmi_perf_protocol_init(struct scmi_handle *handle)
+{
+ int domain;
+ u32 version;
+ struct scmi_perf_info *pinfo;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_PERF, &version);
+
+ dev_dbg(handle->dev, "Performance Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ pinfo = devm_kzalloc(handle->dev, sizeof(*pinfo), GFP_KERNEL);
+ if (!pinfo)
+ return -ENOMEM;
+
+ scmi_perf_attributes_get(handle, pinfo);
+
+ pinfo->dom_info = devm_kcalloc(handle->dev, pinfo->num_domains,
+ sizeof(*pinfo->dom_info), GFP_KERNEL);
+ if (!pinfo->dom_info)
+ return -ENOMEM;
+
+ for (domain = 0; domain < pinfo->num_domains; domain++) {
+ struct perf_dom_info *dom = pinfo->dom_info + domain;
+
+ scmi_perf_domain_attributes_get(handle, domain, dom);
+ scmi_perf_describe_levels_get(handle, domain, dom);
+ }
+
+ handle->perf_ops = &perf_ops;
+ handle->perf_priv = pinfo;
+
+ return 0;
+}
+
+static int __init scmi_perf_init(void)
+{
+ return scmi_protocol_register(SCMI_PROTOCOL_PERF,
+ &scmi_perf_protocol_init);
+}
+subsys_initcall(scmi_perf_init);
diff --git a/drivers/firmware/arm_scmi/power.c b/drivers/firmware/arm_scmi/power.c
new file mode 100644
index 000000000000..087c2876cdf2
--- /dev/null
+++ b/drivers/firmware/arm_scmi/power.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Power Protocol
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include "common.h"
+
+enum scmi_power_protocol_cmd {
+ POWER_DOMAIN_ATTRIBUTES = 0x3,
+ POWER_STATE_SET = 0x4,
+ POWER_STATE_GET = 0x5,
+ POWER_STATE_NOTIFY = 0x6,
+};
+
+struct scmi_msg_resp_power_attributes {
+ __le16 num_domains;
+ __le16 reserved;
+ __le32 stats_addr_low;
+ __le32 stats_addr_high;
+ __le32 stats_size;
+};
+
+struct scmi_msg_resp_power_domain_attributes {
+ __le32 flags;
+#define SUPPORTS_STATE_SET_NOTIFY(x) ((x) & BIT(31))
+#define SUPPORTS_STATE_SET_ASYNC(x) ((x) & BIT(30))
+#define SUPPORTS_STATE_SET_SYNC(x) ((x) & BIT(29))
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_power_set_state {
+ __le32 flags;
+#define STATE_SET_ASYNC BIT(0)
+ __le32 domain;
+ __le32 state;
+};
+
+struct scmi_power_state_notify {
+ __le32 domain;
+ __le32 notify_enable;
+};
+
+struct power_dom_info {
+ bool state_set_sync;
+ bool state_set_async;
+ bool state_set_notify;
+ char name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_power_info {
+ int num_domains;
+ u64 stats_addr;
+ u32 stats_size;
+ struct power_dom_info *dom_info;
+};
+
+static int scmi_power_attributes_get(const struct scmi_handle *handle,
+ struct scmi_power_info *pi)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_power_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_POWER, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ pi->num_domains = le16_to_cpu(attr->num_domains);
+ pi->stats_addr = le32_to_cpu(attr->stats_addr_low) |
+ (u64)le32_to_cpu(attr->stats_addr_high) << 32;
+ pi->stats_size = le32_to_cpu(attr->stats_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_domain_attributes_get(const struct scmi_handle *handle, u32 domain,
+ struct power_dom_info *dom_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_power_domain_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, POWER_DOMAIN_ATTRIBUTES,
+ SCMI_PROTOCOL_POWER, sizeof(domain),
+ sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u32 flags = le32_to_cpu(attr->flags);
+
+ dom_info->state_set_notify = SUPPORTS_STATE_SET_NOTIFY(flags);
+ dom_info->state_set_async = SUPPORTS_STATE_SET_ASYNC(flags);
+ dom_info->state_set_sync = SUPPORTS_STATE_SET_SYNC(flags);
+ memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_state_set(const struct scmi_handle *handle, u32 domain, u32 state)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_power_set_state *st;
+
+ ret = scmi_one_xfer_init(handle, POWER_STATE_SET, SCMI_PROTOCOL_POWER,
+ sizeof(*st), 0, &t);
+ if (ret)
+ return ret;
+
+ st = t->tx.buf;
+ st->flags = cpu_to_le32(0);
+ st->domain = cpu_to_le32(domain);
+ st->state = cpu_to_le32(state);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_state_get(const struct scmi_handle *handle, u32 domain, u32 *state)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, POWER_STATE_GET, SCMI_PROTOCOL_POWER,
+ sizeof(u32), sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ *state = le32_to_cpu(*(__le32 *)t->rx.buf);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_power_num_domains_get(const struct scmi_handle *handle)
+{
+ struct scmi_power_info *pi = handle->power_priv;
+
+ return pi->num_domains;
+}
+
+static char *scmi_power_name_get(const struct scmi_handle *handle, u32 domain)
+{
+ struct scmi_power_info *pi = handle->power_priv;
+ struct power_dom_info *dom = pi->dom_info + domain;
+
+ return dom->name;
+}
+
+static struct scmi_power_ops power_ops = {
+ .num_domains_get = scmi_power_num_domains_get,
+ .name_get = scmi_power_name_get,
+ .state_set = scmi_power_state_set,
+ .state_get = scmi_power_state_get,
+};
+
+static int scmi_power_protocol_init(struct scmi_handle *handle)
+{
+ int domain;
+ u32 version;
+ struct scmi_power_info *pinfo;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_POWER, &version);
+
+ dev_dbg(handle->dev, "Power Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ pinfo = devm_kzalloc(handle->dev, sizeof(*pinfo), GFP_KERNEL);
+ if (!pinfo)
+ return -ENOMEM;
+
+ scmi_power_attributes_get(handle, pinfo);
+
+ pinfo->dom_info = devm_kcalloc(handle->dev, pinfo->num_domains,
+ sizeof(*pinfo->dom_info), GFP_KERNEL);
+ if (!pinfo->dom_info)
+ return -ENOMEM;
+
+ for (domain = 0; domain < pinfo->num_domains; domain++) {
+ struct power_dom_info *dom = pinfo->dom_info + domain;
+
+ scmi_power_domain_attributes_get(handle, domain, dom);
+ }
+
+ handle->power_ops = &power_ops;
+ handle->power_priv = pinfo;
+
+ return 0;
+}
+
+static int __init scmi_power_init(void)
+{
+ return scmi_protocol_register(SCMI_PROTOCOL_POWER,
+ &scmi_power_protocol_init);
+}
+subsys_initcall(scmi_power_init);
diff --git a/drivers/firmware/arm_scmi/scmi_pm_domain.c b/drivers/firmware/arm_scmi/scmi_pm_domain.c
new file mode 100644
index 000000000000..87f737e01473
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_pm_domain.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCMI Generic power domain support.
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pm_domain.h>
+#include <linux/scmi_protocol.h>
+
+struct scmi_pm_domain {
+ struct generic_pm_domain genpd;
+ const struct scmi_handle *handle;
+ const char *name;
+ u32 domain;
+};
+
+#define to_scmi_pd(gpd) container_of(gpd, struct scmi_pm_domain, genpd)
+
+static int scmi_pd_power(struct generic_pm_domain *domain, bool power_on)
+{
+ int ret;
+ u32 state, ret_state;
+ struct scmi_pm_domain *pd = to_scmi_pd(domain);
+ const struct scmi_power_ops *ops = pd->handle->power_ops;
+
+ if (power_on)
+ state = SCMI_POWER_STATE_GENERIC_ON;
+ else
+ state = SCMI_POWER_STATE_GENERIC_OFF;
+
+ ret = ops->state_set(pd->handle, pd->domain, state);
+ if (!ret)
+ ret = ops->state_get(pd->handle, pd->domain, &ret_state);
+ if (!ret && state != ret_state)
+ return -EIO;
+
+ return ret;
+}
+
+static int scmi_pd_power_on(struct generic_pm_domain *domain)
+{
+ return scmi_pd_power(domain, true);
+}
+
+static int scmi_pd_power_off(struct generic_pm_domain *domain)
+{
+ return scmi_pd_power(domain, false);
+}
+
+static int scmi_pm_domain_probe(struct scmi_device *sdev)
+{
+ int num_domains, i;
+ struct device *dev = &sdev->dev;
+ struct device_node *np = dev->of_node;
+ struct scmi_pm_domain *scmi_pd;
+ struct genpd_onecell_data *scmi_pd_data;
+ struct generic_pm_domain **domains;
+ const struct scmi_handle *handle = sdev->handle;
+
+ if (!handle || !handle->power_ops)
+ return -ENODEV;
+
+ num_domains = handle->power_ops->num_domains_get(handle);
+ if (num_domains < 0) {
+ dev_err(dev, "number of domains not found\n");
+ return num_domains;
+ }
+
+ scmi_pd = devm_kcalloc(dev, num_domains, sizeof(*scmi_pd), GFP_KERNEL);
+ if (!scmi_pd)
+ return -ENOMEM;
+
+ scmi_pd_data = devm_kzalloc(dev, sizeof(*scmi_pd_data), GFP_KERNEL);
+ if (!scmi_pd_data)
+ return -ENOMEM;
+
+ domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
+ if (!domains)
+ return -ENOMEM;
+
+ for (i = 0; i < num_domains; i++, scmi_pd++) {
+ u32 state;
+
+ domains[i] = &scmi_pd->genpd;
+
+ scmi_pd->domain = i;
+ scmi_pd->handle = handle;
+ scmi_pd->name = handle->power_ops->name_get(handle, i);
+ scmi_pd->genpd.name = scmi_pd->name;
+ scmi_pd->genpd.power_off = scmi_pd_power_off;
+ scmi_pd->genpd.power_on = scmi_pd_power_on;
+
+ if (handle->power_ops->state_get(handle, i, &state)) {
+ dev_warn(dev, "failed to get state for domain %d\n", i);
+ continue;
+ }
+
+ pm_genpd_init(&scmi_pd->genpd, NULL,
+ state == SCMI_POWER_STATE_GENERIC_OFF);
+ }
+
+ scmi_pd_data->domains = domains;
+ scmi_pd_data->num_domains = num_domains;
+
+ of_genpd_add_provider_onecell(np, scmi_pd_data);
+
+ return 0;
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+ { SCMI_PROTOCOL_POWER },
+ { },
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_power_domain_driver = {
+ .name = "scmi-power-domain",
+ .probe = scmi_pm_domain_probe,
+ .id_table = scmi_id_table,
+};
+module_scmi_driver(scmi_power_domain_driver);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI power domain driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c
new file mode 100644
index 000000000000..bbb469fea0ed
--- /dev/null
+++ b/drivers/firmware/arm_scmi/sensors.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface (SCMI) Sensor Protocol
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ */
+
+#include "common.h"
+
+enum scmi_sensor_protocol_cmd {
+ SENSOR_DESCRIPTION_GET = 0x3,
+ SENSOR_CONFIG_SET = 0x4,
+ SENSOR_TRIP_POINT_SET = 0x5,
+ SENSOR_READING_GET = 0x6,
+};
+
+struct scmi_msg_resp_sensor_attributes {
+ __le16 num_sensors;
+ u8 max_requests;
+ u8 reserved;
+ __le32 reg_addr_low;
+ __le32 reg_addr_high;
+ __le32 reg_size;
+};
+
+struct scmi_msg_resp_sensor_description {
+ __le16 num_returned;
+ __le16 num_remaining;
+ struct {
+ __le32 id;
+ __le32 attributes_low;
+#define SUPPORTS_ASYNC_READ(x) ((x) & BIT(31))
+#define NUM_TRIP_POINTS(x) (((x) >> 4) & 0xff)
+ __le32 attributes_high;
+#define SENSOR_TYPE(x) ((x) & 0xff)
+#define SENSOR_SCALE(x) (((x) >> 11) & 0x3f)
+#define SENSOR_UPDATE_SCALE(x) (((x) >> 22) & 0x1f)
+#define SENSOR_UPDATE_BASE(x) (((x) >> 27) & 0x1f)
+ u8 name[SCMI_MAX_STR_SIZE];
+ } desc[0];
+};
+
+struct scmi_msg_set_sensor_config {
+ __le32 id;
+ __le32 event_control;
+};
+
+struct scmi_msg_set_sensor_trip_point {
+ __le32 id;
+ __le32 event_control;
+#define SENSOR_TP_EVENT_MASK (0x3)
+#define SENSOR_TP_DISABLED 0x0
+#define SENSOR_TP_POSITIVE 0x1
+#define SENSOR_TP_NEGATIVE 0x2
+#define SENSOR_TP_BOTH 0x3
+#define SENSOR_TP_ID(x) (((x) & 0xff) << 4)
+ __le32 value_low;
+ __le32 value_high;
+};
+
+struct scmi_msg_sensor_reading_get {
+ __le32 id;
+ __le32 flags;
+#define SENSOR_READ_ASYNC BIT(0)
+};
+
+struct sensors_info {
+ int num_sensors;
+ int max_requests;
+ u64 reg_addr;
+ u32 reg_size;
+ struct scmi_sensor_info *sensors;
+};
+
+static int scmi_sensor_attributes_get(const struct scmi_handle *handle,
+ struct sensors_info *si)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_sensor_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_SENSOR, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ si->num_sensors = le16_to_cpu(attr->num_sensors);
+ si->max_requests = attr->max_requests;
+ si->reg_addr = le32_to_cpu(attr->reg_addr_low) |
+ (u64)le32_to_cpu(attr->reg_addr_high) << 32;
+ si->reg_size = le32_to_cpu(attr->reg_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_description_get(const struct scmi_handle *handle,
+ struct sensors_info *si)
+{
+ int ret, cnt;
+ u32 desc_index = 0;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_sensor_description *buf;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_DESCRIPTION_GET,
+ SCMI_PROTOCOL_SENSOR, sizeof(__le32), 0, &t);
+ if (ret)
+ return ret;
+
+ buf = t->rx.buf;
+
+ do {
+ /* Set the number of sensors to be skipped/already read */
+ *(__le32 *)t->tx.buf = cpu_to_le32(desc_index);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ num_returned = le16_to_cpu(buf->num_returned);
+ num_remaining = le16_to_cpu(buf->num_remaining);
+
+ if (desc_index + num_returned > si->num_sensors) {
+ dev_err(handle->dev, "No. of sensors can't exceed %d",
+ si->num_sensors);
+ break;
+ }
+
+ for (cnt = 0; cnt < num_returned; cnt++) {
+ u32 attrh;
+ struct scmi_sensor_info *s;
+
+ attrh = le32_to_cpu(buf->desc[cnt].attributes_high);
+ s = &si->sensors[desc_index + cnt];
+ s->id = le32_to_cpu(buf->desc[cnt].id);
+ s->type = SENSOR_TYPE(attrh);
+ memcpy(s->name, buf->desc[cnt].name, SCMI_MAX_STR_SIZE);
+ }
+
+ desc_index += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_sensor_configuration_set(const struct scmi_handle *handle, u32 sensor_id)
+{
+ int ret;
+ u32 evt_cntl = BIT(0);
+ struct scmi_xfer *t;
+ struct scmi_msg_set_sensor_config *cfg;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_CONFIG_SET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(sensor_id);
+ cfg->event_control = cpu_to_le32(evt_cntl);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_trip_point_set(const struct scmi_handle *handle,
+ u32 sensor_id, u8 trip_id, u64 trip_value)
+{
+ int ret;
+ u32 evt_cntl = SENSOR_TP_BOTH;
+ struct scmi_xfer *t;
+ struct scmi_msg_set_sensor_trip_point *trip;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_TRIP_POINT_SET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*trip), 0, &t);
+ if (ret)
+ return ret;
+
+ trip = t->tx.buf;
+ trip->id = cpu_to_le32(sensor_id);
+ trip->event_control = cpu_to_le32(evt_cntl | SENSOR_TP_ID(trip_id));
+ trip->value_low = cpu_to_le32(trip_value & 0xffffffff);
+ trip->value_high = cpu_to_le32(trip_value >> 32);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_reading_get(const struct scmi_handle *handle,
+ u32 sensor_id, bool async, u64 *value)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_sensor_reading_get *sensor;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_READING_GET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*sensor),
+ sizeof(u64), &t);
+ if (ret)
+ return ret;
+
+ sensor = t->tx.buf;
+ sensor->id = cpu_to_le32(sensor_id);
+ sensor->flags = cpu_to_le32(async ? SENSOR_READ_ASYNC : 0);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ __le32 *pval = t->rx.buf;
+
+ *value = le32_to_cpu(*pval);
+ *value |= (u64)le32_to_cpu(*(pval + 1)) << 32;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static const struct scmi_sensor_info *
+scmi_sensor_info_get(const struct scmi_handle *handle, u32 sensor_id)
+{
+ struct sensors_info *si = handle->sensor_priv;
+
+ return si->sensors + sensor_id;
+}
+
+static int scmi_sensor_count_get(const struct scmi_handle *handle)
+{
+ struct sensors_info *si = handle->sensor_priv;
+
+ return si->num_sensors;
+}
+
+static struct scmi_sensor_ops sensor_ops = {
+ .count_get = scmi_sensor_count_get,
+ .info_get = scmi_sensor_info_get,
+ .configuration_set = scmi_sensor_configuration_set,
+ .trip_point_set = scmi_sensor_trip_point_set,
+ .reading_get = scmi_sensor_reading_get,
+};
+
+static int scmi_sensors_protocol_init(struct scmi_handle *handle)
+{
+ u32 version;
+ struct sensors_info *sinfo;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_SENSOR, &version);
+
+ dev_dbg(handle->dev, "Sensor Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ sinfo = devm_kzalloc(handle->dev, sizeof(*sinfo), GFP_KERNEL);
+ if (!sinfo)
+ return -ENOMEM;
+
+ scmi_sensor_attributes_get(handle, sinfo);
+
+ sinfo->sensors = devm_kcalloc(handle->dev, sinfo->num_sensors,
+ sizeof(*sinfo->sensors), GFP_KERNEL);
+ if (!sinfo->sensors)
+ return -ENOMEM;
+
+ scmi_sensor_description_get(handle, sinfo);
+
+ handle->sensor_ops = &sensor_ops;
+ handle->sensor_priv = sinfo;
+
+ return 0;
+}
+
+static int __init scmi_sensors_init(void)
+{
+ return scmi_protocol_register(SCMI_PROTOCOL_SENSOR,
+ &scmi_sensors_protocol_init);
+}
+subsys_initcall(scmi_sensors_init);
diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c
index 7da9f1b83ebe..6d7a6c0a5e07 100644
--- a/drivers/firmware/arm_scpi.c
+++ b/drivers/firmware/arm_scpi.c
@@ -28,6 +28,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bitmap.h>
+#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
@@ -45,48 +46,32 @@
#include <linux/sort.h>
#include <linux/spinlock.h>
-#define CMD_ID_SHIFT 0
-#define CMD_ID_MASK 0x7f
-#define CMD_TOKEN_ID_SHIFT 8
-#define CMD_TOKEN_ID_MASK 0xff
-#define CMD_DATA_SIZE_SHIFT 16
-#define CMD_DATA_SIZE_MASK 0x1ff
-#define CMD_LEGACY_DATA_SIZE_SHIFT 20
-#define CMD_LEGACY_DATA_SIZE_MASK 0x1ff
-#define PACK_SCPI_CMD(cmd_id, tx_sz) \
- ((((cmd_id) & CMD_ID_MASK) << CMD_ID_SHIFT) | \
- (((tx_sz) & CMD_DATA_SIZE_MASK) << CMD_DATA_SIZE_SHIFT))
-#define ADD_SCPI_TOKEN(cmd, token) \
- ((cmd) |= (((token) & CMD_TOKEN_ID_MASK) << CMD_TOKEN_ID_SHIFT))
-#define PACK_LEGACY_SCPI_CMD(cmd_id, tx_sz) \
- ((((cmd_id) & CMD_ID_MASK) << CMD_ID_SHIFT) | \
- (((tx_sz) & CMD_LEGACY_DATA_SIZE_MASK) << CMD_LEGACY_DATA_SIZE_SHIFT))
-
-#define CMD_SIZE(cmd) (((cmd) >> CMD_DATA_SIZE_SHIFT) & CMD_DATA_SIZE_MASK)
-#define CMD_LEGACY_SIZE(cmd) (((cmd) >> CMD_LEGACY_DATA_SIZE_SHIFT) & \
- CMD_LEGACY_DATA_SIZE_MASK)
-#define CMD_UNIQ_MASK (CMD_TOKEN_ID_MASK << CMD_TOKEN_ID_SHIFT | CMD_ID_MASK)
+#define CMD_ID_MASK GENMASK(6, 0)
+#define CMD_TOKEN_ID_MASK GENMASK(15, 8)
+#define CMD_DATA_SIZE_MASK GENMASK(24, 16)
+#define CMD_LEGACY_DATA_SIZE_MASK GENMASK(28, 20)
+#define PACK_SCPI_CMD(cmd_id, tx_sz) \
+ (FIELD_PREP(CMD_ID_MASK, cmd_id) | \
+ FIELD_PREP(CMD_DATA_SIZE_MASK, tx_sz))
+#define PACK_LEGACY_SCPI_CMD(cmd_id, tx_sz) \
+ (FIELD_PREP(CMD_ID_MASK, cmd_id) | \
+ FIELD_PREP(CMD_LEGACY_DATA_SIZE_MASK, tx_sz))
+
+#define CMD_SIZE(cmd) FIELD_GET(CMD_DATA_SIZE_MASK, cmd)
+#define CMD_UNIQ_MASK (CMD_TOKEN_ID_MASK | CMD_ID_MASK)
#define CMD_XTRACT_UNIQ(cmd) ((cmd) & CMD_UNIQ_MASK)
#define SCPI_SLOT 0
#define MAX_DVFS_DOMAINS 8
#define MAX_DVFS_OPPS 16
-#define DVFS_LATENCY(hdr) (le32_to_cpu(hdr) >> 16)
-#define DVFS_OPP_COUNT(hdr) ((le32_to_cpu(hdr) >> 8) & 0xff)
-
-#define PROTOCOL_REV_MINOR_BITS 16
-#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1)
-#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
-#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
-
-#define FW_REV_MAJOR_BITS 24
-#define FW_REV_MINOR_BITS 16
-#define FW_REV_PATCH_MASK ((1U << FW_REV_MINOR_BITS) - 1)
-#define FW_REV_MINOR_MASK ((1U << FW_REV_MAJOR_BITS) - 1)
-#define FW_REV_MAJOR(x) ((x) >> FW_REV_MAJOR_BITS)
-#define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS)
-#define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK)
+
+#define PROTO_REV_MAJOR_MASK GENMASK(31, 16)
+#define PROTO_REV_MINOR_MASK GENMASK(15, 0)
+
+#define FW_REV_MAJOR_MASK GENMASK(31, 24)
+#define FW_REV_MINOR_MASK GENMASK(23, 16)
+#define FW_REV_PATCH_MASK GENMASK(15, 0)
#define MAX_RX_TIMEOUT (msecs_to_jiffies(30))
@@ -311,10 +296,6 @@ struct clk_get_info {
u8 name[20];
} __packed;
-struct clk_get_value {
- __le32 rate;
-} __packed;
-
struct clk_set_value {
__le16 id;
__le16 reserved;
@@ -328,7 +309,9 @@ struct legacy_clk_set_value {
} __packed;
struct dvfs_info {
- __le32 header;
+ u8 domain;
+ u8 opp_count;
+ __le16 latency;
struct {
__le32 freq;
__le32 m_volt;
@@ -340,10 +323,6 @@ struct dvfs_set {
u8 index;
} __packed;
-struct sensor_capabilities {
- __le16 sensors;
-} __packed;
-
struct _scpi_sensor_info {
__le16 sensor_id;
u8 class;
@@ -351,11 +330,6 @@ struct _scpi_sensor_info {
char name[20];
};
-struct sensor_value {
- __le32 lo_val;
- __le32 hi_val;
-} __packed;
-
struct dev_pstate_set {
__le16 dev_id;
u8 pstate;
@@ -419,19 +393,20 @@ static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd)
unsigned int len;
if (scpi_info->is_legacy) {
- struct legacy_scpi_shared_mem *mem = ch->rx_payload;
+ struct legacy_scpi_shared_mem __iomem *mem =
+ ch->rx_payload;
/* RX Length is not replied by the legacy Firmware */
len = match->rx_len;
- match->status = le32_to_cpu(mem->status);
+ match->status = ioread32(&mem->status);
memcpy_fromio(match->rx_buf, mem->payload, len);
} else {
- struct scpi_shared_mem *mem = ch->rx_payload;
+ struct scpi_shared_mem __iomem *mem = ch->rx_payload;
- len = min(match->rx_len, CMD_SIZE(cmd));
+ len = min_t(unsigned int, match->rx_len, CMD_SIZE(cmd));
- match->status = le32_to_cpu(mem->status);
+ match->status = ioread32(&mem->status);
memcpy_fromio(match->rx_buf, mem->payload, len);
}
@@ -445,11 +420,11 @@ static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd)
static void scpi_handle_remote_msg(struct mbox_client *c, void *msg)
{
struct scpi_chan *ch = container_of(c, struct scpi_chan, cl);
- struct scpi_shared_mem *mem = ch->rx_payload;
+ struct scpi_shared_mem __iomem *mem = ch->rx_payload;
u32 cmd = 0;
if (!scpi_info->is_legacy)
- cmd = le32_to_cpu(mem->command);
+ cmd = ioread32(&mem->command);
scpi_process_cmd(ch, cmd);
}
@@ -459,7 +434,7 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg)
unsigned long flags;
struct scpi_xfer *t = msg;
struct scpi_chan *ch = container_of(c, struct scpi_chan, cl);
- struct scpi_shared_mem *mem = (struct scpi_shared_mem *)ch->tx_payload;
+ struct scpi_shared_mem __iomem *mem = ch->tx_payload;
if (t->tx_buf) {
if (scpi_info->is_legacy)
@@ -471,14 +446,14 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg)
if (t->rx_buf) {
if (!(++ch->token))
++ch->token;
- ADD_SCPI_TOKEN(t->cmd, ch->token);
+ t->cmd |= FIELD_PREP(CMD_TOKEN_ID_MASK, ch->token);
spin_lock_irqsave(&ch->rx_lock, flags);
list_add_tail(&t->node, &ch->rx_pending);
spin_unlock_irqrestore(&ch->rx_lock, flags);
}
if (!scpi_info->is_legacy)
- mem->command = cpu_to_le32(t->cmd);
+ iowrite32(t->cmd, &mem->command);
}
static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch)
@@ -583,13 +558,13 @@ scpi_clk_get_range(u16 clk_id, unsigned long *min, unsigned long *max)
static unsigned long scpi_clk_get_val(u16 clk_id)
{
int ret;
- struct clk_get_value clk;
+ __le32 rate;
__le16 le_clk_id = cpu_to_le16(clk_id);
ret = scpi_send_message(CMD_GET_CLOCK_VALUE, &le_clk_id,
- sizeof(le_clk_id), &clk, sizeof(clk));
+ sizeof(le_clk_id), &rate, sizeof(rate));
- return ret ? ret : le32_to_cpu(clk.rate);
+ return ret ? ret : le32_to_cpu(rate);
}
static int scpi_clk_set_val(u16 clk_id, unsigned long rate)
@@ -665,8 +640,8 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain)
if (!info)
return ERR_PTR(-ENOMEM);
- info->count = DVFS_OPP_COUNT(buf.header);
- info->latency = DVFS_LATENCY(buf.header) * 1000; /* uS to nS */
+ info->count = buf.opp_count;
+ info->latency = le16_to_cpu(buf.latency) * 1000; /* uS to nS */
info->opps = kcalloc(info->count, sizeof(*opp), GFP_KERNEL);
if (!info->opps) {
@@ -713,9 +688,6 @@ static int scpi_dvfs_get_transition_latency(struct device *dev)
if (IS_ERR(info))
return PTR_ERR(info);
- if (!info->latency)
- return 0;
-
return info->latency;
}
@@ -746,13 +718,13 @@ static int scpi_dvfs_add_opps_to_device(struct device *dev)
static int scpi_sensor_get_capability(u16 *sensors)
{
- struct sensor_capabilities cap_buf;
+ __le16 cap;
int ret;
- ret = scpi_send_message(CMD_SENSOR_CAPABILITIES, NULL, 0, &cap_buf,
- sizeof(cap_buf));
+ ret = scpi_send_message(CMD_SENSOR_CAPABILITIES, NULL, 0, &cap,
+ sizeof(cap));
if (!ret)
- *sensors = le16_to_cpu(cap_buf.sensors);
+ *sensors = le16_to_cpu(cap);
return ret;
}
@@ -776,20 +748,19 @@ static int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info)
static int scpi_sensor_get_value(u16 sensor, u64 *val)
{
__le16 id = cpu_to_le16(sensor);
- struct sensor_value buf;
+ __le64 value;
int ret;
ret = scpi_send_message(CMD_SENSOR_VALUE, &id, sizeof(id),
- &buf, sizeof(buf));
+ &value, sizeof(value));
if (ret)
return ret;
if (scpi_info->is_legacy)
- /* only 32-bits supported, hi_val can be junk */
- *val = le32_to_cpu(buf.lo_val);
+ /* only 32-bits supported, upper 32 bits can be junk */
+ *val = le32_to_cpup((__le32 *)&value);
else
- *val = (u64)le32_to_cpu(buf.hi_val) << 32 |
- le32_to_cpu(buf.lo_val);
+ *val = le64_to_cpu(value);
return 0;
}
@@ -864,9 +835,9 @@ static ssize_t protocol_version_show(struct device *dev,
{
struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev);
- return sprintf(buf, "%d.%d\n",
- PROTOCOL_REV_MAJOR(scpi_info->protocol_version),
- PROTOCOL_REV_MINOR(scpi_info->protocol_version));
+ return sprintf(buf, "%lu.%lu\n",
+ FIELD_GET(PROTO_REV_MAJOR_MASK, scpi_info->protocol_version),
+ FIELD_GET(PROTO_REV_MINOR_MASK, scpi_info->protocol_version));
}
static DEVICE_ATTR_RO(protocol_version);
@@ -875,10 +846,10 @@ static ssize_t firmware_version_show(struct device *dev,
{
struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev);
- return sprintf(buf, "%d.%d.%d\n",
- FW_REV_MAJOR(scpi_info->firmware_version),
- FW_REV_MINOR(scpi_info->firmware_version),
- FW_REV_PATCH(scpi_info->firmware_version));
+ return sprintf(buf, "%lu.%lu.%lu\n",
+ FIELD_GET(FW_REV_MAJOR_MASK, scpi_info->firmware_version),
+ FIELD_GET(FW_REV_MINOR_MASK, scpi_info->firmware_version),
+ FIELD_GET(FW_REV_PATCH_MASK, scpi_info->firmware_version));
}
static DEVICE_ATTR_RO(firmware_version);
@@ -889,37 +860,26 @@ static struct attribute *versions_attrs[] = {
};
ATTRIBUTE_GROUPS(versions);
-static void
-scpi_free_channels(struct device *dev, struct scpi_chan *pchan, int count)
+static void scpi_free_channels(void *data)
{
+ struct scpi_drvinfo *info = data;
int i;
- for (i = 0; i < count && pchan->chan; i++, pchan++) {
- mbox_free_channel(pchan->chan);
- devm_kfree(dev, pchan->xfers);
- devm_iounmap(dev, pchan->rx_payload);
- }
+ for (i = 0; i < info->num_chans; i++)
+ mbox_free_channel(info->channels[i].chan);
}
static int scpi_remove(struct platform_device *pdev)
{
int i;
- struct device *dev = &pdev->dev;
struct scpi_drvinfo *info = platform_get_drvdata(pdev);
scpi_info = NULL; /* stop exporting SCPI ops through get_scpi_ops */
- of_platform_depopulate(dev);
- sysfs_remove_groups(&dev->kobj, versions_groups);
- scpi_free_channels(dev, info->channels, info->num_chans);
- platform_set_drvdata(pdev, NULL);
-
for (i = 0; i < MAX_DVFS_DOMAINS && info->dvfs[i]; i++) {
kfree(info->dvfs[i]->opps);
kfree(info->dvfs[i]);
}
- devm_kfree(dev, info->channels);
- devm_kfree(dev, info);
return 0;
}
@@ -952,7 +912,6 @@ static int scpi_probe(struct platform_device *pdev)
{
int count, idx, ret;
struct resource res;
- struct scpi_chan *scpi_chan;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
@@ -969,13 +928,19 @@ static int scpi_probe(struct platform_device *pdev)
return -ENODEV;
}
- scpi_chan = devm_kcalloc(dev, count, sizeof(*scpi_chan), GFP_KERNEL);
- if (!scpi_chan)
+ scpi_info->channels = devm_kcalloc(dev, count, sizeof(struct scpi_chan),
+ GFP_KERNEL);
+ if (!scpi_info->channels)
return -ENOMEM;
- for (idx = 0; idx < count; idx++) {
+ ret = devm_add_action(dev, scpi_free_channels, scpi_info);
+ if (ret)
+ return ret;
+
+ for (; scpi_info->num_chans < count; scpi_info->num_chans++) {
resource_size_t size;
- struct scpi_chan *pchan = scpi_chan + idx;
+ int idx = scpi_info->num_chans;
+ struct scpi_chan *pchan = scpi_info->channels + idx;
struct mbox_client *cl = &pchan->cl;
struct device_node *shmem = of_parse_phandle(np, "shmem", idx);
@@ -983,15 +948,14 @@ static int scpi_probe(struct platform_device *pdev)
of_node_put(shmem);
if (ret) {
dev_err(dev, "failed to get SCPI payload mem resource\n");
- goto err;
+ return ret;
}
size = resource_size(&res);
pchan->rx_payload = devm_ioremap(dev, res.start, size);
if (!pchan->rx_payload) {
dev_err(dev, "failed to ioremap SCPI payload\n");
- ret = -EADDRNOTAVAIL;
- goto err;
+ return -EADDRNOTAVAIL;
}
pchan->tx_payload = pchan->rx_payload + (size >> 1);
@@ -1017,14 +981,9 @@ static int scpi_probe(struct platform_device *pdev)
dev_err(dev, "failed to get channel%d err %d\n",
idx, ret);
}
-err:
- scpi_free_channels(dev, scpi_chan, idx);
- scpi_info = NULL;
return ret;
}
- scpi_info->channels = scpi_chan;
- scpi_info->num_chans = count;
scpi_info->commands = scpi_std_commands;
platform_set_drvdata(pdev, scpi_info);
@@ -1043,23 +1002,31 @@ err:
ret = scpi_init_versions(scpi_info);
if (ret) {
dev_err(dev, "incorrect or no SCP firmware found\n");
- scpi_remove(pdev);
return ret;
}
- _dev_info(dev, "SCP Protocol %d.%d Firmware %d.%d.%d version\n",
- PROTOCOL_REV_MAJOR(scpi_info->protocol_version),
- PROTOCOL_REV_MINOR(scpi_info->protocol_version),
- FW_REV_MAJOR(scpi_info->firmware_version),
- FW_REV_MINOR(scpi_info->firmware_version),
- FW_REV_PATCH(scpi_info->firmware_version));
+ if (scpi_info->is_legacy && !scpi_info->protocol_version &&
+ !scpi_info->firmware_version)
+ dev_info(dev, "SCP Protocol legacy pre-1.0 firmware\n");
+ else
+ dev_info(dev, "SCP Protocol %lu.%lu Firmware %lu.%lu.%lu version\n",
+ FIELD_GET(PROTO_REV_MAJOR_MASK,
+ scpi_info->protocol_version),
+ FIELD_GET(PROTO_REV_MINOR_MASK,
+ scpi_info->protocol_version),
+ FIELD_GET(FW_REV_MAJOR_MASK,
+ scpi_info->firmware_version),
+ FIELD_GET(FW_REV_MINOR_MASK,
+ scpi_info->firmware_version),
+ FIELD_GET(FW_REV_PATCH_MASK,
+ scpi_info->firmware_version));
scpi_info->scpi_ops = &scpi_ops;
- ret = sysfs_create_groups(&dev->kobj, versions_groups);
+ ret = devm_device_add_groups(dev, versions_groups);
if (ret)
dev_err(dev, "unable to create sysfs version group\n");
- return of_platform_populate(dev->of_node, NULL, NULL, dev);
+ return devm_of_platform_populate(dev);
}
static const struct of_device_id scpi_of_match[] = {
diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c
index ff204421117b..0ec2ca87318c 100644
--- a/drivers/firmware/meson/meson_sm.c
+++ b/drivers/firmware/meson/meson_sm.c
@@ -17,8 +17,10 @@
#include <linux/arm-smccc.h>
#include <linux/bug.h>
#include <linux/io.h>
+#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/sizes.h>
@@ -217,21 +219,11 @@ static const struct of_device_id meson_sm_ids[] = {
{ /* sentinel */ },
};
-int __init meson_sm_init(void)
+static int __init meson_sm_probe(struct platform_device *pdev)
{
const struct meson_sm_chip *chip;
- const struct of_device_id *matched_np;
- struct device_node *np;
- np = of_find_matching_node_and_match(NULL, meson_sm_ids, &matched_np);
- if (!np)
- return -ENODEV;
-
- chip = matched_np->data;
- if (!chip) {
- pr_err("unable to setup secure-monitor data\n");
- goto out;
- }
+ chip = of_match_device(meson_sm_ids, &pdev->dev)->data;
if (chip->cmd_shmem_in_base) {
fw.sm_shmem_in_base = meson_sm_map_shmem(chip->cmd_shmem_in_base,
@@ -257,4 +249,11 @@ out_in_base:
out:
return -EINVAL;
}
-device_initcall(meson_sm_init);
+
+static struct platform_driver meson_sm_driver = {
+ .driver = {
+ .name = "meson-sm",
+ .of_match_table = of_match_ptr(meson_sm_ids),
+ },
+};
+module_platform_driver_probe(meson_sm_driver, meson_sm_probe);
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index a7f461f2e650..14a456afa379 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -70,57 +70,20 @@ void tegra_bpmp_put(struct tegra_bpmp *bpmp)
}
EXPORT_SYMBOL_GPL(tegra_bpmp_put);
-static int tegra_bpmp_channel_get_index(struct tegra_bpmp_channel *channel)
-{
- return channel - channel->bpmp->channels;
-}
-
static int
tegra_bpmp_channel_get_thread_index(struct tegra_bpmp_channel *channel)
{
struct tegra_bpmp *bpmp = channel->bpmp;
- unsigned int offset, count;
+ unsigned int count;
int index;
- offset = bpmp->soc->channels.thread.offset;
count = bpmp->soc->channels.thread.count;
- index = tegra_bpmp_channel_get_index(channel);
- if (index < 0)
- return index;
-
- if (index < offset || index >= offset + count)
+ index = channel - channel->bpmp->threaded_channels;
+ if (index < 0 || index >= count)
return -EINVAL;
- return index - offset;
-}
-
-static struct tegra_bpmp_channel *
-tegra_bpmp_channel_get_thread(struct tegra_bpmp *bpmp, unsigned int index)
-{
- unsigned int offset = bpmp->soc->channels.thread.offset;
- unsigned int count = bpmp->soc->channels.thread.count;
-
- if (index >= count)
- return NULL;
-
- return &bpmp->channels[offset + index];
-}
-
-static struct tegra_bpmp_channel *
-tegra_bpmp_channel_get_tx(struct tegra_bpmp *bpmp)
-{
- unsigned int offset = bpmp->soc->channels.cpu_tx.offset;
-
- return &bpmp->channels[offset + smp_processor_id()];
-}
-
-static struct tegra_bpmp_channel *
-tegra_bpmp_channel_get_rx(struct tegra_bpmp *bpmp)
-{
- unsigned int offset = bpmp->soc->channels.cpu_rx.offset;
-
- return &bpmp->channels[offset];
+ return index;
}
static bool tegra_bpmp_message_valid(const struct tegra_bpmp_message *msg)
@@ -271,11 +234,7 @@ tegra_bpmp_write_threaded(struct tegra_bpmp *bpmp, unsigned int mrq,
goto unlock;
}
- channel = tegra_bpmp_channel_get_thread(bpmp, index);
- if (!channel) {
- err = -EINVAL;
- goto unlock;
- }
+ channel = &bpmp->threaded_channels[index];
if (!tegra_bpmp_master_free(channel)) {
err = -EBUSY;
@@ -328,12 +287,18 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
if (!tegra_bpmp_message_valid(msg))
return -EINVAL;
- channel = tegra_bpmp_channel_get_tx(bpmp);
+ channel = bpmp->tx_channel;
+
+ spin_lock(&bpmp->atomic_tx_lock);
err = tegra_bpmp_channel_write(channel, msg->mrq, MSG_ACK,
msg->tx.data, msg->tx.size);
- if (err < 0)
+ if (err < 0) {
+ spin_unlock(&bpmp->atomic_tx_lock);
return err;
+ }
+
+ spin_unlock(&bpmp->atomic_tx_lock);
err = mbox_send_message(bpmp->mbox.channel, NULL);
if (err < 0)
@@ -607,7 +572,7 @@ static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data)
unsigned int i, count;
unsigned long *busy;
- channel = tegra_bpmp_channel_get_rx(bpmp);
+ channel = bpmp->rx_channel;
count = bpmp->soc->channels.thread.count;
busy = bpmp->threaded.busy;
@@ -619,9 +584,7 @@ static void tegra_bpmp_handle_rx(struct mbox_client *client, void *data)
for_each_set_bit(i, busy, count) {
struct tegra_bpmp_channel *channel;
- channel = tegra_bpmp_channel_get_thread(bpmp, i);
- if (!channel)
- continue;
+ channel = &bpmp->threaded_channels[i];
if (tegra_bpmp_master_acked(channel)) {
tegra_bpmp_channel_signal(channel);
@@ -698,7 +661,6 @@ static void tegra_bpmp_channel_cleanup(struct tegra_bpmp_channel *channel)
static int tegra_bpmp_probe(struct platform_device *pdev)
{
- struct tegra_bpmp_channel *channel;
struct tegra_bpmp *bpmp;
unsigned int i;
char tag[32];
@@ -732,7 +694,7 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
}
bpmp->rx.virt = gen_pool_dma_alloc(bpmp->rx.pool, 4096, &bpmp->rx.phys);
- if (!bpmp->rx.pool) {
+ if (!bpmp->rx.virt) {
dev_err(&pdev->dev, "failed to allocate from RX pool\n");
err = -ENOMEM;
goto free_tx;
@@ -758,24 +720,45 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
goto free_rx;
}
- bpmp->num_channels = bpmp->soc->channels.cpu_tx.count +
- bpmp->soc->channels.thread.count +
- bpmp->soc->channels.cpu_rx.count;
+ spin_lock_init(&bpmp->atomic_tx_lock);
+ bpmp->tx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->tx_channel),
+ GFP_KERNEL);
+ if (!bpmp->tx_channel) {
+ err = -ENOMEM;
+ goto free_rx;
+ }
- bpmp->channels = devm_kcalloc(&pdev->dev, bpmp->num_channels,
- sizeof(*channel), GFP_KERNEL);
- if (!bpmp->channels) {
+ bpmp->rx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->rx_channel),
+ GFP_KERNEL);
+ if (!bpmp->rx_channel) {
err = -ENOMEM;
goto free_rx;
}
- /* message channel initialization */
- for (i = 0; i < bpmp->num_channels; i++) {
- struct tegra_bpmp_channel *channel = &bpmp->channels[i];
+ bpmp->threaded_channels = devm_kcalloc(&pdev->dev, bpmp->threaded.count,
+ sizeof(*bpmp->threaded_channels),
+ GFP_KERNEL);
+ if (!bpmp->threaded_channels) {
+ err = -ENOMEM;
+ goto free_rx;
+ }
- err = tegra_bpmp_channel_init(channel, bpmp, i);
+ err = tegra_bpmp_channel_init(bpmp->tx_channel, bpmp,
+ bpmp->soc->channels.cpu_tx.offset);
+ if (err < 0)
+ goto free_rx;
+
+ err = tegra_bpmp_channel_init(bpmp->rx_channel, bpmp,
+ bpmp->soc->channels.cpu_rx.offset);
+ if (err < 0)
+ goto cleanup_tx_channel;
+
+ for (i = 0; i < bpmp->threaded.count; i++) {
+ err = tegra_bpmp_channel_init(
+ &bpmp->threaded_channels[i], bpmp,
+ bpmp->soc->channels.thread.offset + i);
if (err < 0)
- goto cleanup_channels;
+ goto cleanup_threaded_channels;
}
/* mbox registration */
@@ -788,15 +771,14 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
if (IS_ERR(bpmp->mbox.channel)) {
err = PTR_ERR(bpmp->mbox.channel);
dev_err(&pdev->dev, "failed to get HSP mailbox: %d\n", err);
- goto cleanup_channels;
+ goto cleanup_threaded_channels;
}
/* reset message channels */
- for (i = 0; i < bpmp->num_channels; i++) {
- struct tegra_bpmp_channel *channel = &bpmp->channels[i];
-
- tegra_bpmp_channel_reset(channel);
- }
+ tegra_bpmp_channel_reset(bpmp->tx_channel);
+ tegra_bpmp_channel_reset(bpmp->rx_channel);
+ for (i = 0; i < bpmp->threaded.count; i++)
+ tegra_bpmp_channel_reset(&bpmp->threaded_channels[i]);
err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
tegra_bpmp_mrq_handle_ping, bpmp);
@@ -845,9 +827,15 @@ free_mrq:
tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
free_mbox:
mbox_free_channel(bpmp->mbox.channel);
-cleanup_channels:
- while (i--)
- tegra_bpmp_channel_cleanup(&bpmp->channels[i]);
+cleanup_threaded_channels:
+ for (i = 0; i < bpmp->threaded.count; i++) {
+ if (bpmp->threaded_channels[i].bpmp)
+ tegra_bpmp_channel_cleanup(&bpmp->threaded_channels[i]);
+ }
+
+ tegra_bpmp_channel_cleanup(bpmp->rx_channel);
+cleanup_tx_channel:
+ tegra_bpmp_channel_cleanup(bpmp->tx_channel);
free_rx:
gen_pool_free(bpmp->rx.pool, (unsigned long)bpmp->rx.virt, 4096);
free_tx:
@@ -858,18 +846,16 @@ free_tx:
static const struct tegra_bpmp_soc tegra186_soc = {
.channels = {
.cpu_tx = {
- .offset = 0,
- .count = 6,
+ .offset = 3,
.timeout = 60 * USEC_PER_SEC,
},
.thread = {
- .offset = 6,
- .count = 7,
+ .offset = 0,
+ .count = 3,
.timeout = 600 * USEC_PER_SEC,
},
.cpu_rx = {
.offset = 13,
- .count = 1,
.timeout = 0,
},
},
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index ef23553ff5cb..033e57366d56 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -317,6 +317,18 @@ config SENSORS_APPLESMC
Say Y here if you have an applicable laptop and want to experience
the awesome power of applesmc.
+config SENSORS_ARM_SCMI
+ tristate "ARM SCMI Sensors"
+ depends on ARM_SCMI_PROTOCOL
+ depends on THERMAL || !THERMAL_OF
+ help
+ This driver provides support for temperature, voltage, current
+ and power sensors available on SCMI based platforms. The actual
+ number and type of sensors exported depend on the platform.
+
+ This driver can also be built as a module. If so, the module
+ will be called scmi-hwmon.
+
config SENSORS_ARM_SCPI
tristate "ARM SCPI Sensors"
depends on ARM_SCPI_PROTOCOL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index f814b4ace138..e7d52a36e6c4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
+obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
diff --git a/drivers/hwmon/scmi-hwmon.c b/drivers/hwmon/scmi-hwmon.c
new file mode 100644
index 000000000000..363bf56eb0f2
--- /dev/null
+++ b/drivers/hwmon/scmi-hwmon.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * System Control and Management Interface(SCMI) based hwmon sensor driver
+ *
+ * Copyright (C) 2018 ARM Ltd.
+ * Sudeep Holla <sudeep.holla@arm.com>
+ */
+
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/scmi_protocol.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+
+struct scmi_sensors {
+ const struct scmi_handle *handle;
+ const struct scmi_sensor_info **info[hwmon_max];
+};
+
+static int scmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ int ret;
+ u64 value;
+ const struct scmi_sensor_info *sensor;
+ struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);
+ const struct scmi_handle *h = scmi_sensors->handle;
+
+ sensor = *(scmi_sensors->info[type] + channel);
+ ret = h->sensor_ops->reading_get(h, sensor->id, false, &value);
+ if (!ret)
+ *val = value;
+
+ return ret;
+}
+
+static int
+scmi_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ const struct scmi_sensor_info *sensor;
+ struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);
+
+ sensor = *(scmi_sensors->info[type] + channel);
+ *str = sensor->name;
+
+ return 0;
+}
+
+static umode_t
+scmi_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct scmi_sensor_info *sensor;
+ const struct scmi_sensors *scmi_sensors = drvdata;
+
+ sensor = *(scmi_sensors->info[type] + channel);
+ if (sensor && sensor->name)
+ return S_IRUGO;
+
+ return 0;
+}
+
+static const struct hwmon_ops scmi_hwmon_ops = {
+ .is_visible = scmi_hwmon_is_visible,
+ .read = scmi_hwmon_read,
+ .read_string = scmi_hwmon_read_string,
+};
+
+static struct hwmon_chip_info scmi_chip_info = {
+ .ops = &scmi_hwmon_ops,
+ .info = NULL,
+};
+
+static int scmi_hwmon_add_chan_info(struct hwmon_channel_info *scmi_hwmon_chan,
+ struct device *dev, int num,
+ enum hwmon_sensor_types type, u32 config)
+{
+ int i;
+ u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL);
+
+ if (!cfg)
+ return -ENOMEM;
+
+ scmi_hwmon_chan->type = type;
+ scmi_hwmon_chan->config = cfg;
+ for (i = 0; i < num; i++, cfg++)
+ *cfg = config;
+
+ return 0;
+}
+
+static enum hwmon_sensor_types scmi_types[] = {
+ [TEMPERATURE_C] = hwmon_temp,
+ [VOLTAGE] = hwmon_in,
+ [CURRENT] = hwmon_curr,
+ [POWER] = hwmon_power,
+ [ENERGY] = hwmon_energy,
+};
+
+static u32 hwmon_attributes[] = {
+ [hwmon_chip] = HWMON_C_REGISTER_TZ,
+ [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL,
+ [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL,
+ [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL,
+ [hwmon_power] = HWMON_P_INPUT | HWMON_P_LABEL,
+ [hwmon_energy] = HWMON_E_INPUT | HWMON_E_LABEL,
+};
+
+static int scmi_hwmon_probe(struct scmi_device *sdev)
+{
+ int i, idx;
+ u16 nr_sensors;
+ enum hwmon_sensor_types type;
+ struct scmi_sensors *scmi_sensors;
+ const struct scmi_sensor_info *sensor;
+ int nr_count[hwmon_max] = {0}, nr_types = 0;
+ const struct hwmon_chip_info *chip_info;
+ struct device *hwdev, *dev = &sdev->dev;
+ struct hwmon_channel_info *scmi_hwmon_chan;
+ const struct hwmon_channel_info **ptr_scmi_ci;
+ const struct scmi_handle *handle = sdev->handle;
+
+ if (!handle || !handle->sensor_ops)
+ return -ENODEV;
+
+ nr_sensors = handle->sensor_ops->count_get(handle);
+ if (!nr_sensors)
+ return -EIO;
+
+ scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL);
+ if (!scmi_sensors)
+ return -ENOMEM;
+
+ scmi_sensors->handle = handle;
+
+ for (i = 0; i < nr_sensors; i++) {
+ sensor = handle->sensor_ops->info_get(handle, i);
+ if (!sensor)
+ return -EINVAL;
+
+ switch (sensor->type) {
+ case TEMPERATURE_C:
+ case VOLTAGE:
+ case CURRENT:
+ case POWER:
+ case ENERGY:
+ type = scmi_types[sensor->type];
+ if (!nr_count[type])
+ nr_types++;
+ nr_count[type]++;
+ break;
+ }
+ }
+
+ if (nr_count[hwmon_temp])
+ nr_count[hwmon_chip]++, nr_types++;
+
+ scmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*scmi_hwmon_chan),
+ GFP_KERNEL);
+ if (!scmi_hwmon_chan)
+ return -ENOMEM;
+
+ ptr_scmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*ptr_scmi_ci),
+ GFP_KERNEL);
+ if (!ptr_scmi_ci)
+ return -ENOMEM;
+
+ scmi_chip_info.info = ptr_scmi_ci;
+ chip_info = &scmi_chip_info;
+
+ for (type = 0; type < hwmon_max && nr_count[type]; type++) {
+ scmi_hwmon_add_chan_info(scmi_hwmon_chan, dev, nr_count[type],
+ type, hwmon_attributes[type]);
+ *ptr_scmi_ci++ = scmi_hwmon_chan++;
+
+ scmi_sensors->info[type] =
+ devm_kcalloc(dev, nr_count[type],
+ sizeof(*scmi_sensors->info), GFP_KERNEL);
+ if (!scmi_sensors->info[type])
+ return -ENOMEM;
+ }
+
+ for (i = nr_sensors - 1; i >= 0 ; i--) {
+ sensor = handle->sensor_ops->info_get(handle, i);
+ if (!sensor)
+ continue;
+
+ switch (sensor->type) {
+ case TEMPERATURE_C:
+ case VOLTAGE:
+ case CURRENT:
+ case POWER:
+ case ENERGY:
+ type = scmi_types[sensor->type];
+ idx = --nr_count[type];
+ *(scmi_sensors->info[type] + idx) = sensor;
+ break;
+ }
+ }
+
+ hwdev = devm_hwmon_device_register_with_info(dev, "scmi_sensors",
+ scmi_sensors, chip_info,
+ NULL);
+
+ return PTR_ERR_OR_ZERO(hwdev);
+}
+
+static const struct scmi_device_id scmi_id_table[] = {
+ { SCMI_PROTOCOL_SENSOR },
+ { },
+};
+MODULE_DEVICE_TABLE(scmi, scmi_id_table);
+
+static struct scmi_driver scmi_hwmon_drv = {
+ .name = "scmi-hwmon",
+ .probe = scmi_hwmon_probe,
+ .id_table = scmi_id_table,
+};
+module_scmi_driver(scmi_hwmon_drv);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI HWMON interface driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c
index 04644e7b42b1..2f214440008c 100644
--- a/drivers/memory/emif.c
+++ b/drivers/memory/emif.c
@@ -127,7 +127,7 @@ static int emif_regdump_show(struct seq_file *s, void *unused)
for (i = 0; i < EMIF_MAX_NUM_FREQUENCIES && regs_cache[i]; i++) {
do_emif_regdump_show(s, emif, regs_cache[i]);
- seq_printf(s, "\n");
+ seq_putc(s, '\n');
}
return 0;
diff --git a/drivers/memory/samsung/Kconfig b/drivers/memory/samsung/Kconfig
index 9de12222061c..79ce7ea58903 100644
--- a/drivers/memory/samsung/Kconfig
+++ b/drivers/memory/samsung/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
config SAMSUNG_MC
bool "Samsung Exynos Memory Controller support" if COMPILE_TEST
help
diff --git a/drivers/memory/samsung/Makefile b/drivers/memory/samsung/Makefile
index 9c554d5522ad..00587be66211 100644
--- a/drivers/memory/samsung/Makefile
+++ b/drivers/memory/samsung/Makefile
@@ -1 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_EXYNOS_SROM) += exynos-srom.o
diff --git a/drivers/memory/samsung/exynos-srom.c b/drivers/memory/samsung/exynos-srom.c
index bf827a666694..7edd7fb540f2 100644
--- a/drivers/memory/samsung/exynos-srom.c
+++ b/drivers/memory/samsung/exynos-srom.c
@@ -1,14 +1,10 @@
-/*
- * Copyright (c) 2015 Samsung Electronics Co., Ltd.
- * http://www.samsung.com/
- *
- * EXYNOS - SROM Controller support
- * Author: Pankaj Dubey <pankaj.dubey@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (c) 2015 Samsung Electronics Co., Ltd.
+// http://www.samsung.com/
+//
+// EXYNOS - SROM Controller support
+// Author: Pankaj Dubey <pankaj.dubey@samsung.com>
#include <linux/io.h>
#include <linux/init.h>
diff --git a/drivers/memory/samsung/exynos-srom.h b/drivers/memory/samsung/exynos-srom.h
index 34660c6a57a9..da612797f522 100644
--- a/drivers/memory/samsung/exynos-srom.h
+++ b/drivers/memory/samsung/exynos-srom.h
@@ -1,13 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Exynos SROMC register definitions
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
-*/
+ */
#ifndef __EXYNOS_SROM_H
#define __EXYNOS_SROM_H __FILE__
diff --git a/drivers/memory/ti-emif-pm.c b/drivers/memory/ti-emif-pm.c
index 62a86c4bcd0b..632651f4b6e8 100644
--- a/drivers/memory/ti-emif-pm.c
+++ b/drivers/memory/ti-emif-pm.c
@@ -271,7 +271,6 @@ static int ti_emif_probe(struct platform_device *pdev)
emif_data->pm_data.ti_emif_base_addr_virt = devm_ioremap_resource(dev,
res);
if (IS_ERR(emif_data->pm_data.ti_emif_base_addr_virt)) {
- dev_err(dev, "could not ioremap emif mem\n");
ret = PTR_ERR(emif_data->pm_data.ti_emif_base_addr_virt);
return ret;
}
diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
index da5724cd89cf..28bb5a029558 100644
--- a/drivers/perf/Kconfig
+++ b/drivers/perf/Kconfig
@@ -5,6 +5,39 @@
menu "Performance monitor support"
depends on PERF_EVENTS
+config ARM_CCI_PMU
+ bool
+ select ARM_CCI
+
+config ARM_CCI400_PMU
+ bool "ARM CCI400 PMU support"
+ depends on (ARM && CPU_V7) || ARM64
+ select ARM_CCI400_COMMON
+ select ARM_CCI_PMU
+ help
+ Support for PMU events monitoring on the ARM CCI-400 (cache coherent
+ interconnect). CCI-400 supports counting events related to the
+ connected slave/master interfaces.
+
+config ARM_CCI5xx_PMU
+ bool "ARM CCI-500/CCI-550 PMU support"
+ depends on (ARM && CPU_V7) || ARM64
+ select ARM_CCI_PMU
+ help
+ Support for PMU events monitoring on the ARM CCI-500/CCI-550 cache
+ coherent interconnects. Both of them provide 8 independent event counters,
+ which can count events pertaining to the slave/master interfaces as well
+ as the internal events to the CCI.
+
+ If unsure, say Y
+
+config ARM_CCN
+ tristate "ARM CCN driver support"
+ depends on ARM || ARM64
+ help
+ PMU (perf) driver supporting the ARM CCN (Cache Coherent Network)
+ interconnect.
+
config ARM_PMU
depends on ARM || ARM64
bool "ARM PMU framework"
diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
index c2f27419bdf0..b3902bd37d53 100644
--- a/drivers/perf/Makefile
+++ b/drivers/perf/Makefile
@@ -1,4 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_ARM_CCI_PMU) += arm-cci.o
+obj-$(CONFIG_ARM_CCN) += arm-ccn.o
obj-$(CONFIG_ARM_DSU_PMU) += arm_dsu_pmu.o
obj-$(CONFIG_ARM_PMU) += arm_pmu.o arm_pmu_platform.o
obj-$(CONFIG_ARM_PMU_ACPI) += arm_pmu_acpi.o
diff --git a/drivers/perf/arm-cci.c b/drivers/perf/arm-cci.c
new file mode 100644
index 000000000000..383b2d3dcbc6
--- /dev/null
+++ b/drivers/perf/arm-cci.c
@@ -0,0 +1,1722 @@
+// SPDX-License-Identifier: GPL-2.0
+// CCI Cache Coherent Interconnect PMU driver
+// Copyright (C) 2013-2018 Arm Ltd.
+// Author: Punit Agrawal <punit.agrawal@arm.com>, Suzuki Poulose <suzuki.poulose@arm.com>
+
+#include <linux/arm-cci.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/perf_event.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define DRIVER_NAME "ARM-CCI PMU"
+
+#define CCI_PMCR 0x0100
+#define CCI_PID2 0x0fe8
+
+#define CCI_PMCR_CEN 0x00000001
+#define CCI_PMCR_NCNT_MASK 0x0000f800
+#define CCI_PMCR_NCNT_SHIFT 11
+
+#define CCI_PID2_REV_MASK 0xf0
+#define CCI_PID2_REV_SHIFT 4
+
+#define CCI_PMU_EVT_SEL 0x000
+#define CCI_PMU_CNTR 0x004
+#define CCI_PMU_CNTR_CTRL 0x008
+#define CCI_PMU_OVRFLW 0x00c
+
+#define CCI_PMU_OVRFLW_FLAG 1
+
+#define CCI_PMU_CNTR_SIZE(model) ((model)->cntr_size)
+#define CCI_PMU_CNTR_BASE(model, idx) ((idx) * CCI_PMU_CNTR_SIZE(model))
+#define CCI_PMU_CNTR_MASK ((1ULL << 32) -1)
+#define CCI_PMU_CNTR_LAST(cci_pmu) (cci_pmu->num_cntrs - 1)
+
+#define CCI_PMU_MAX_HW_CNTRS(model) \
+ ((model)->num_hw_cntrs + (model)->fixed_hw_cntrs)
+
+/* Types of interfaces that can generate events */
+enum {
+ CCI_IF_SLAVE,
+ CCI_IF_MASTER,
+#ifdef CONFIG_ARM_CCI5xx_PMU
+ CCI_IF_GLOBAL,
+#endif
+ CCI_IF_MAX,
+};
+
+struct event_range {
+ u32 min;
+ u32 max;
+};
+
+struct cci_pmu_hw_events {
+ struct perf_event **events;
+ unsigned long *used_mask;
+ raw_spinlock_t pmu_lock;
+};
+
+struct cci_pmu;
+/*
+ * struct cci_pmu_model:
+ * @fixed_hw_cntrs - Number of fixed event counters
+ * @num_hw_cntrs - Maximum number of programmable event counters
+ * @cntr_size - Size of an event counter mapping
+ */
+struct cci_pmu_model {
+ char *name;
+ u32 fixed_hw_cntrs;
+ u32 num_hw_cntrs;
+ u32 cntr_size;
+ struct attribute **format_attrs;
+ struct attribute **event_attrs;
+ struct event_range event_ranges[CCI_IF_MAX];
+ int (*validate_hw_event)(struct cci_pmu *, unsigned long);
+ int (*get_event_idx)(struct cci_pmu *, struct cci_pmu_hw_events *, unsigned long);
+ void (*write_counters)(struct cci_pmu *, unsigned long *);
+};
+
+static struct cci_pmu_model cci_pmu_models[];
+
+struct cci_pmu {
+ void __iomem *base;
+ void __iomem *ctrl_base;
+ struct pmu pmu;
+ int cpu;
+ int nr_irqs;
+ int *irqs;
+ unsigned long active_irqs;
+ const struct cci_pmu_model *model;
+ struct cci_pmu_hw_events hw_events;
+ struct platform_device *plat_device;
+ int num_cntrs;
+ atomic_t active_events;
+ struct mutex reserve_mutex;
+};
+
+#define to_cci_pmu(c) (container_of(c, struct cci_pmu, pmu))
+
+static struct cci_pmu *g_cci_pmu;
+
+enum cci_models {
+#ifdef CONFIG_ARM_CCI400_PMU
+ CCI400_R0,
+ CCI400_R1,
+#endif
+#ifdef CONFIG_ARM_CCI5xx_PMU
+ CCI500_R0,
+ CCI550_R0,
+#endif
+ CCI_MODEL_MAX
+};
+
+static void pmu_write_counters(struct cci_pmu *cci_pmu,
+ unsigned long *mask);
+static ssize_t cci_pmu_format_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t cci_pmu_event_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+#define CCI_EXT_ATTR_ENTRY(_name, _func, _config) \
+ &((struct dev_ext_attribute[]) { \
+ { __ATTR(_name, S_IRUGO, _func, NULL), (void *)_config } \
+ })[0].attr.attr
+
+#define CCI_FORMAT_EXT_ATTR_ENTRY(_name, _config) \
+ CCI_EXT_ATTR_ENTRY(_name, cci_pmu_format_show, (char *)_config)
+#define CCI_EVENT_EXT_ATTR_ENTRY(_name, _config) \
+ CCI_EXT_ATTR_ENTRY(_name, cci_pmu_event_show, (unsigned long)_config)
+
+/* CCI400 PMU Specific definitions */
+
+#ifdef CONFIG_ARM_CCI400_PMU
+
+/* Port ids */
+#define CCI400_PORT_S0 0
+#define CCI400_PORT_S1 1
+#define CCI400_PORT_S2 2
+#define CCI400_PORT_S3 3
+#define CCI400_PORT_S4 4
+#define CCI400_PORT_M0 5
+#define CCI400_PORT_M1 6
+#define CCI400_PORT_M2 7
+
+#define CCI400_R1_PX 5
+
+/*
+ * Instead of an event id to monitor CCI cycles, a dedicated counter is
+ * provided. Use 0xff to represent CCI cycles and hope that no future revisions
+ * make use of this event in hardware.
+ */
+enum cci400_perf_events {
+ CCI400_PMU_CYCLES = 0xff
+};
+
+#define CCI400_PMU_CYCLE_CNTR_IDX 0
+#define CCI400_PMU_CNTR0_IDX 1
+
+/*
+ * CCI PMU event id is an 8-bit value made of two parts - bits 7:5 for one of 8
+ * ports and bits 4:0 are event codes. There are different event codes
+ * associated with each port type.
+ *
+ * Additionally, the range of events associated with the port types changed
+ * between Rev0 and Rev1.
+ *
+ * The constants below define the range of valid codes for each port type for
+ * the different revisions and are used to validate the event to be monitored.
+ */
+
+#define CCI400_PMU_EVENT_MASK 0xffUL
+#define CCI400_PMU_EVENT_SOURCE_SHIFT 5
+#define CCI400_PMU_EVENT_SOURCE_MASK 0x7
+#define CCI400_PMU_EVENT_CODE_SHIFT 0
+#define CCI400_PMU_EVENT_CODE_MASK 0x1f
+#define CCI400_PMU_EVENT_SOURCE(event) \
+ ((event >> CCI400_PMU_EVENT_SOURCE_SHIFT) & \
+ CCI400_PMU_EVENT_SOURCE_MASK)
+#define CCI400_PMU_EVENT_CODE(event) \
+ ((event >> CCI400_PMU_EVENT_CODE_SHIFT) & CCI400_PMU_EVENT_CODE_MASK)
+
+#define CCI400_R0_SLAVE_PORT_MIN_EV 0x00
+#define CCI400_R0_SLAVE_PORT_MAX_EV 0x13
+#define CCI400_R0_MASTER_PORT_MIN_EV 0x14
+#define CCI400_R0_MASTER_PORT_MAX_EV 0x1a
+
+#define CCI400_R1_SLAVE_PORT_MIN_EV 0x00
+#define CCI400_R1_SLAVE_PORT_MAX_EV 0x14
+#define CCI400_R1_MASTER_PORT_MIN_EV 0x00
+#define CCI400_R1_MASTER_PORT_MAX_EV 0x11
+
+#define CCI400_CYCLE_EVENT_EXT_ATTR_ENTRY(_name, _config) \
+ CCI_EXT_ATTR_ENTRY(_name, cci400_pmu_cycle_event_show, \
+ (unsigned long)_config)
+
+static ssize_t cci400_pmu_cycle_event_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static struct attribute *cci400_pmu_format_attrs[] = {
+ CCI_FORMAT_EXT_ATTR_ENTRY(event, "config:0-4"),
+ CCI_FORMAT_EXT_ATTR_ENTRY(source, "config:5-7"),
+ NULL
+};
+
+static struct attribute *cci400_r0_pmu_event_attrs[] = {
+ /* Slave events */
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_any, 0x0),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_device, 0x01),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_normal_or_nonshareable, 0x2),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_inner_or_outershareable, 0x3),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_cache_maintenance, 0x4),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_mem_barrier, 0x5),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_sync_barrier, 0x6),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg, 0x7),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg_sync, 0x8),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_stall_tt_full, 0x9),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_last_hs_snoop, 0xA),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_stall_rvalids_h_rready_l, 0xB),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_any, 0xC),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_device, 0xD),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_normal_or_nonshareable, 0xE),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_inner_or_outershare_wback_wclean, 0xF),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_write_unique, 0x10),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_write_line_unique, 0x11),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_evict, 0x12),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_stall_tt_full, 0x13),
+ /* Master events */
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_retry_speculative_fetch, 0x14),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_addr_hazard, 0x15),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_id_hazard, 0x16),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_tt_full, 0x17),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_barrier_hazard, 0x18),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_barrier_hazard, 0x19),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_tt_full, 0x1A),
+ /* Special event for cycles counter */
+ CCI400_CYCLE_EVENT_EXT_ATTR_ENTRY(cycles, 0xff),
+ NULL
+};
+
+static struct attribute *cci400_r1_pmu_event_attrs[] = {
+ /* Slave events */
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_any, 0x0),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_device, 0x01),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_normal_or_nonshareable, 0x2),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_inner_or_outershareable, 0x3),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_cache_maintenance, 0x4),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_mem_barrier, 0x5),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_sync_barrier, 0x6),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg, 0x7),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg_sync, 0x8),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_stall_tt_full, 0x9),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_last_hs_snoop, 0xA),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_stall_rvalids_h_rready_l, 0xB),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_any, 0xC),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_device, 0xD),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_normal_or_nonshareable, 0xE),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_inner_or_outershare_wback_wclean, 0xF),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_write_unique, 0x10),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_write_line_unique, 0x11),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_evict, 0x12),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_stall_tt_full, 0x13),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_stall_slave_id_hazard, 0x14),
+ /* Master events */
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_retry_speculative_fetch, 0x0),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_stall_cycle_addr_hazard, 0x1),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_master_id_hazard, 0x2),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_hi_prio_rtq_full, 0x3),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_barrier_hazard, 0x4),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_barrier_hazard, 0x5),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_wtq_full, 0x6),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_low_prio_rtq_full, 0x7),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_mid_prio_rtq_full, 0x8),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_qvn_vn0, 0x9),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_qvn_vn1, 0xA),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_qvn_vn2, 0xB),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall_qvn_vn3, 0xC),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_qvn_vn0, 0xD),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_qvn_vn1, 0xE),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_qvn_vn2, 0xF),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall_qvn_vn3, 0x10),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_unique_or_line_unique_addr_hazard, 0x11),
+ /* Special event for cycles counter */
+ CCI400_CYCLE_EVENT_EXT_ATTR_ENTRY(cycles, 0xff),
+ NULL
+};
+
+static ssize_t cci400_pmu_cycle_event_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *eattr = container_of(attr,
+ struct dev_ext_attribute, attr);
+ return snprintf(buf, PAGE_SIZE, "config=0x%lx\n", (unsigned long)eattr->var);
+}
+
+static int cci400_get_event_idx(struct cci_pmu *cci_pmu,
+ struct cci_pmu_hw_events *hw,
+ unsigned long cci_event)
+{
+ int idx;
+
+ /* cycles event idx is fixed */
+ if (cci_event == CCI400_PMU_CYCLES) {
+ if (test_and_set_bit(CCI400_PMU_CYCLE_CNTR_IDX, hw->used_mask))
+ return -EAGAIN;
+
+ return CCI400_PMU_CYCLE_CNTR_IDX;
+ }
+
+ for (idx = CCI400_PMU_CNTR0_IDX; idx <= CCI_PMU_CNTR_LAST(cci_pmu); ++idx)
+ if (!test_and_set_bit(idx, hw->used_mask))
+ return idx;
+
+ /* No counters available */
+ return -EAGAIN;
+}
+
+static int cci400_validate_hw_event(struct cci_pmu *cci_pmu, unsigned long hw_event)
+{
+ u8 ev_source = CCI400_PMU_EVENT_SOURCE(hw_event);
+ u8 ev_code = CCI400_PMU_EVENT_CODE(hw_event);
+ int if_type;
+
+ if (hw_event & ~CCI400_PMU_EVENT_MASK)
+ return -ENOENT;
+
+ if (hw_event == CCI400_PMU_CYCLES)
+ return hw_event;
+
+ switch (ev_source) {
+ case CCI400_PORT_S0:
+ case CCI400_PORT_S1:
+ case CCI400_PORT_S2:
+ case CCI400_PORT_S3:
+ case CCI400_PORT_S4:
+ /* Slave Interface */
+ if_type = CCI_IF_SLAVE;
+ break;
+ case CCI400_PORT_M0:
+ case CCI400_PORT_M1:
+ case CCI400_PORT_M2:
+ /* Master Interface */
+ if_type = CCI_IF_MASTER;
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (ev_code >= cci_pmu->model->event_ranges[if_type].min &&
+ ev_code <= cci_pmu->model->event_ranges[if_type].max)
+ return hw_event;
+
+ return -ENOENT;
+}
+
+static int probe_cci400_revision(struct cci_pmu *cci_pmu)
+{
+ int rev;
+ rev = readl_relaxed(cci_pmu->ctrl_base + CCI_PID2) & CCI_PID2_REV_MASK;
+ rev >>= CCI_PID2_REV_SHIFT;
+
+ if (rev < CCI400_R1_PX)
+ return CCI400_R0;
+ else
+ return CCI400_R1;
+}
+
+static const struct cci_pmu_model *probe_cci_model(struct cci_pmu *cci_pmu)
+{
+ if (platform_has_secure_cci_access())
+ return &cci_pmu_models[probe_cci400_revision(cci_pmu)];
+ return NULL;
+}
+#else /* !CONFIG_ARM_CCI400_PMU */
+static inline struct cci_pmu_model *probe_cci_model(struct cci_pmu *cci_pmu)
+{
+ return NULL;
+}
+#endif /* CONFIG_ARM_CCI400_PMU */
+
+#ifdef CONFIG_ARM_CCI5xx_PMU
+
+/*
+ * CCI5xx PMU event id is an 9-bit value made of two parts.
+ * bits [8:5] - Source for the event
+ * bits [4:0] - Event code (specific to type of interface)
+ *
+ *
+ */
+
+/* Port ids */
+#define CCI5xx_PORT_S0 0x0
+#define CCI5xx_PORT_S1 0x1
+#define CCI5xx_PORT_S2 0x2
+#define CCI5xx_PORT_S3 0x3
+#define CCI5xx_PORT_S4 0x4
+#define CCI5xx_PORT_S5 0x5
+#define CCI5xx_PORT_S6 0x6
+
+#define CCI5xx_PORT_M0 0x8
+#define CCI5xx_PORT_M1 0x9
+#define CCI5xx_PORT_M2 0xa
+#define CCI5xx_PORT_M3 0xb
+#define CCI5xx_PORT_M4 0xc
+#define CCI5xx_PORT_M5 0xd
+#define CCI5xx_PORT_M6 0xe
+
+#define CCI5xx_PORT_GLOBAL 0xf
+
+#define CCI5xx_PMU_EVENT_MASK 0x1ffUL
+#define CCI5xx_PMU_EVENT_SOURCE_SHIFT 0x5
+#define CCI5xx_PMU_EVENT_SOURCE_MASK 0xf
+#define CCI5xx_PMU_EVENT_CODE_SHIFT 0x0
+#define CCI5xx_PMU_EVENT_CODE_MASK 0x1f
+
+#define CCI5xx_PMU_EVENT_SOURCE(event) \
+ ((event >> CCI5xx_PMU_EVENT_SOURCE_SHIFT) & CCI5xx_PMU_EVENT_SOURCE_MASK)
+#define CCI5xx_PMU_EVENT_CODE(event) \
+ ((event >> CCI5xx_PMU_EVENT_CODE_SHIFT) & CCI5xx_PMU_EVENT_CODE_MASK)
+
+#define CCI5xx_SLAVE_PORT_MIN_EV 0x00
+#define CCI5xx_SLAVE_PORT_MAX_EV 0x1f
+#define CCI5xx_MASTER_PORT_MIN_EV 0x00
+#define CCI5xx_MASTER_PORT_MAX_EV 0x06
+#define CCI5xx_GLOBAL_PORT_MIN_EV 0x00
+#define CCI5xx_GLOBAL_PORT_MAX_EV 0x0f
+
+
+#define CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(_name, _config) \
+ CCI_EXT_ATTR_ENTRY(_name, cci5xx_pmu_global_event_show, \
+ (unsigned long) _config)
+
+static ssize_t cci5xx_pmu_global_event_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+
+static struct attribute *cci5xx_pmu_format_attrs[] = {
+ CCI_FORMAT_EXT_ATTR_ENTRY(event, "config:0-4"),
+ CCI_FORMAT_EXT_ATTR_ENTRY(source, "config:5-8"),
+ NULL,
+};
+
+static struct attribute *cci5xx_pmu_event_attrs[] = {
+ /* Slave events */
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_arvalid, 0x0),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_dev, 0x1),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_nonshareable, 0x2),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_shareable_non_alloc, 0x3),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_shareable_alloc, 0x4),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_invalidate, 0x5),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_cache_maint, 0x6),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_dvm_msg, 0x7),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_rval, 0x8),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_hs_rlast_snoop, 0x9),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_hs_awalid, 0xA),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_dev, 0xB),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_non_shareable, 0xC),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_share_wb, 0xD),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_share_wlu, 0xE),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_share_wunique, 0xF),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_evict, 0x10),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_wrevict, 0x11),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_w_data_beat, 0x12),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_srq_acvalid, 0x13),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_srq_read, 0x14),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_srq_clean, 0x15),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_srq_data_transfer_low, 0x16),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rrq_stall_arvalid, 0x17),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_r_data_stall, 0x18),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_wrq_stall, 0x19),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_w_data_stall, 0x1A),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_w_resp_stall, 0x1B),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_srq_stall, 0x1C),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_s_data_stall, 0x1D),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_rq_stall_ot_limit, 0x1E),
+ CCI_EVENT_EXT_ATTR_ENTRY(si_r_stall_arbit, 0x1F),
+
+ /* Master events */
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_r_data_beat_any, 0x0),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_w_data_beat_any, 0x1),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_rrq_stall, 0x2),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_r_data_stall, 0x3),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_wrq_stall, 0x4),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_w_data_stall, 0x5),
+ CCI_EVENT_EXT_ATTR_ENTRY(mi_w_resp_stall, 0x6),
+
+ /* Global events */
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_filter_bank_0_1, 0x0),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_filter_bank_2_3, 0x1),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_filter_bank_4_5, 0x2),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_filter_bank_6_7, 0x3),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_miss_filter_bank_0_1, 0x4),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_miss_filter_bank_2_3, 0x5),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_miss_filter_bank_4_5, 0x6),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_access_miss_filter_bank_6_7, 0x7),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_back_invalidation, 0x8),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_stall_alloc_busy, 0x9),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_stall_tt_full, 0xA),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_wrq, 0xB),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_cd_hs, 0xC),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_rq_stall_addr_hazard, 0xD),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_rq_stall_tt_full, 0xE),
+ CCI5xx_GLOBAL_EVENT_EXT_ATTR_ENTRY(cci_snoop_rq_tzmp1_prot, 0xF),
+ NULL
+};
+
+static ssize_t cci5xx_pmu_global_event_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *eattr = container_of(attr,
+ struct dev_ext_attribute, attr);
+ /* Global events have single fixed source code */
+ return snprintf(buf, PAGE_SIZE, "event=0x%lx,source=0x%x\n",
+ (unsigned long)eattr->var, CCI5xx_PORT_GLOBAL);
+}
+
+/*
+ * CCI500 provides 8 independent event counters that can count
+ * any of the events available.
+ * CCI500 PMU event source ids
+ * 0x0-0x6 - Slave interfaces
+ * 0x8-0xD - Master interfaces
+ * 0xf - Global Events
+ * 0x7,0xe - Reserved
+ */
+static int cci500_validate_hw_event(struct cci_pmu *cci_pmu,
+ unsigned long hw_event)
+{
+ u32 ev_source = CCI5xx_PMU_EVENT_SOURCE(hw_event);
+ u32 ev_code = CCI5xx_PMU_EVENT_CODE(hw_event);
+ int if_type;
+
+ if (hw_event & ~CCI5xx_PMU_EVENT_MASK)
+ return -ENOENT;
+
+ switch (ev_source) {
+ case CCI5xx_PORT_S0:
+ case CCI5xx_PORT_S1:
+ case CCI5xx_PORT_S2:
+ case CCI5xx_PORT_S3:
+ case CCI5xx_PORT_S4:
+ case CCI5xx_PORT_S5:
+ case CCI5xx_PORT_S6:
+ if_type = CCI_IF_SLAVE;
+ break;
+ case CCI5xx_PORT_M0:
+ case CCI5xx_PORT_M1:
+ case CCI5xx_PORT_M2:
+ case CCI5xx_PORT_M3:
+ case CCI5xx_PORT_M4:
+ case CCI5xx_PORT_M5:
+ if_type = CCI_IF_MASTER;
+ break;
+ case CCI5xx_PORT_GLOBAL:
+ if_type = CCI_IF_GLOBAL;
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (ev_code >= cci_pmu->model->event_ranges[if_type].min &&
+ ev_code <= cci_pmu->model->event_ranges[if_type].max)
+ return hw_event;
+
+ return -ENOENT;
+}
+
+/*
+ * CCI550 provides 8 independent event counters that can count
+ * any of the events available.
+ * CCI550 PMU event source ids
+ * 0x0-0x6 - Slave interfaces
+ * 0x8-0xe - Master interfaces
+ * 0xf - Global Events
+ * 0x7 - Reserved
+ */
+static int cci550_validate_hw_event(struct cci_pmu *cci_pmu,
+ unsigned long hw_event)
+{
+ u32 ev_source = CCI5xx_PMU_EVENT_SOURCE(hw_event);
+ u32 ev_code = CCI5xx_PMU_EVENT_CODE(hw_event);
+ int if_type;
+
+ if (hw_event & ~CCI5xx_PMU_EVENT_MASK)
+ return -ENOENT;
+
+ switch (ev_source) {
+ case CCI5xx_PORT_S0:
+ case CCI5xx_PORT_S1:
+ case CCI5xx_PORT_S2:
+ case CCI5xx_PORT_S3:
+ case CCI5xx_PORT_S4:
+ case CCI5xx_PORT_S5:
+ case CCI5xx_PORT_S6:
+ if_type = CCI_IF_SLAVE;
+ break;
+ case CCI5xx_PORT_M0:
+ case CCI5xx_PORT_M1:
+ case CCI5xx_PORT_M2:
+ case CCI5xx_PORT_M3:
+ case CCI5xx_PORT_M4:
+ case CCI5xx_PORT_M5:
+ case CCI5xx_PORT_M6:
+ if_type = CCI_IF_MASTER;
+ break;
+ case CCI5xx_PORT_GLOBAL:
+ if_type = CCI_IF_GLOBAL;
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (ev_code >= cci_pmu->model->event_ranges[if_type].min &&
+ ev_code <= cci_pmu->model->event_ranges[if_type].max)
+ return hw_event;
+
+ return -ENOENT;
+}
+
+#endif /* CONFIG_ARM_CCI5xx_PMU */
+
+/*
+ * Program the CCI PMU counters which have PERF_HES_ARCH set
+ * with the event period and mark them ready before we enable
+ * PMU.
+ */
+static void cci_pmu_sync_counters(struct cci_pmu *cci_pmu)
+{
+ int i;
+ struct cci_pmu_hw_events *cci_hw = &cci_pmu->hw_events;
+
+ DECLARE_BITMAP(mask, cci_pmu->num_cntrs);
+
+ bitmap_zero(mask, cci_pmu->num_cntrs);
+ for_each_set_bit(i, cci_pmu->hw_events.used_mask, cci_pmu->num_cntrs) {
+ struct perf_event *event = cci_hw->events[i];
+
+ if (WARN_ON(!event))
+ continue;
+
+ /* Leave the events which are not counting */
+ if (event->hw.state & PERF_HES_STOPPED)
+ continue;
+ if (event->hw.state & PERF_HES_ARCH) {
+ set_bit(i, mask);
+ event->hw.state &= ~PERF_HES_ARCH;
+ }
+ }
+
+ pmu_write_counters(cci_pmu, mask);
+}
+
+/* Should be called with cci_pmu->hw_events->pmu_lock held */
+static void __cci_pmu_enable_nosync(struct cci_pmu *cci_pmu)
+{
+ u32 val;
+
+ /* Enable all the PMU counters. */
+ val = readl_relaxed(cci_pmu->ctrl_base + CCI_PMCR) | CCI_PMCR_CEN;
+ writel(val, cci_pmu->ctrl_base + CCI_PMCR);
+}
+
+/* Should be called with cci_pmu->hw_events->pmu_lock held */
+static void __cci_pmu_enable_sync(struct cci_pmu *cci_pmu)
+{
+ cci_pmu_sync_counters(cci_pmu);
+ __cci_pmu_enable_nosync(cci_pmu);
+}
+
+/* Should be called with cci_pmu->hw_events->pmu_lock held */
+static void __cci_pmu_disable(struct cci_pmu *cci_pmu)
+{
+ u32 val;
+
+ /* Disable all the PMU counters. */
+ val = readl_relaxed(cci_pmu->ctrl_base + CCI_PMCR) & ~CCI_PMCR_CEN;
+ writel(val, cci_pmu->ctrl_base + CCI_PMCR);
+}
+
+static ssize_t cci_pmu_format_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *eattr = container_of(attr,
+ struct dev_ext_attribute, attr);
+ return snprintf(buf, PAGE_SIZE, "%s\n", (char *)eattr->var);
+}
+
+static ssize_t cci_pmu_event_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dev_ext_attribute *eattr = container_of(attr,
+ struct dev_ext_attribute, attr);
+ /* source parameter is mandatory for normal PMU events */
+ return snprintf(buf, PAGE_SIZE, "source=?,event=0x%lx\n",
+ (unsigned long)eattr->var);
+}
+
+static int pmu_is_valid_counter(struct cci_pmu *cci_pmu, int idx)
+{
+ return 0 <= idx && idx <= CCI_PMU_CNTR_LAST(cci_pmu);
+}
+
+static u32 pmu_read_register(struct cci_pmu *cci_pmu, int idx, unsigned int offset)
+{
+ return readl_relaxed(cci_pmu->base +
+ CCI_PMU_CNTR_BASE(cci_pmu->model, idx) + offset);
+}
+
+static void pmu_write_register(struct cci_pmu *cci_pmu, u32 value,
+ int idx, unsigned int offset)
+{
+ writel_relaxed(value, cci_pmu->base +
+ CCI_PMU_CNTR_BASE(cci_pmu->model, idx) + offset);
+}
+
+static void pmu_disable_counter(struct cci_pmu *cci_pmu, int idx)
+{
+ pmu_write_register(cci_pmu, 0, idx, CCI_PMU_CNTR_CTRL);
+}
+
+static void pmu_enable_counter(struct cci_pmu *cci_pmu, int idx)
+{
+ pmu_write_register(cci_pmu, 1, idx, CCI_PMU_CNTR_CTRL);
+}
+
+static bool __maybe_unused
+pmu_counter_is_enabled(struct cci_pmu *cci_pmu, int idx)
+{
+ return (pmu_read_register(cci_pmu, idx, CCI_PMU_CNTR_CTRL) & 0x1) != 0;
+}
+
+static void pmu_set_event(struct cci_pmu *cci_pmu, int idx, unsigned long event)
+{
+ pmu_write_register(cci_pmu, event, idx, CCI_PMU_EVT_SEL);
+}
+
+/*
+ * For all counters on the CCI-PMU, disable any 'enabled' counters,
+ * saving the changed counters in the mask, so that we can restore
+ * it later using pmu_restore_counters. The mask is private to the
+ * caller. We cannot rely on the used_mask maintained by the CCI_PMU
+ * as it only tells us if the counter is assigned to perf_event or not.
+ * The state of the perf_event cannot be locked by the PMU layer, hence
+ * we check the individual counter status (which can be locked by
+ * cci_pm->hw_events->pmu_lock).
+ *
+ * @mask should be initialised to empty by the caller.
+ */
+static void __maybe_unused
+pmu_save_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
+{
+ int i;
+
+ for (i = 0; i < cci_pmu->num_cntrs; i++) {
+ if (pmu_counter_is_enabled(cci_pmu, i)) {
+ set_bit(i, mask);
+ pmu_disable_counter(cci_pmu, i);
+ }
+ }
+}
+
+/*
+ * Restore the status of the counters. Reversal of the pmu_save_counters().
+ * For each counter set in the mask, enable the counter back.
+ */
+static void __maybe_unused
+pmu_restore_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
+{
+ int i;
+
+ for_each_set_bit(i, mask, cci_pmu->num_cntrs)
+ pmu_enable_counter(cci_pmu, i);
+}
+
+/*
+ * Returns the number of programmable counters actually implemented
+ * by the cci
+ */
+static u32 pmu_get_max_counters(struct cci_pmu *cci_pmu)
+{
+ return (readl_relaxed(cci_pmu->ctrl_base + CCI_PMCR) &
+ CCI_PMCR_NCNT_MASK) >> CCI_PMCR_NCNT_SHIFT;
+}
+
+static int pmu_get_event_idx(struct cci_pmu_hw_events *hw, struct perf_event *event)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ unsigned long cci_event = event->hw.config_base;
+ int idx;
+
+ if (cci_pmu->model->get_event_idx)
+ return cci_pmu->model->get_event_idx(cci_pmu, hw, cci_event);
+
+ /* Generic code to find an unused idx from the mask */
+ for(idx = 0; idx <= CCI_PMU_CNTR_LAST(cci_pmu); idx++)
+ if (!test_and_set_bit(idx, hw->used_mask))
+ return idx;
+
+ /* No counters available */
+ return -EAGAIN;
+}
+
+static int pmu_map_event(struct perf_event *event)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+
+ if (event->attr.type < PERF_TYPE_MAX ||
+ !cci_pmu->model->validate_hw_event)
+ return -ENOENT;
+
+ return cci_pmu->model->validate_hw_event(cci_pmu, event->attr.config);
+}
+
+static int pmu_request_irq(struct cci_pmu *cci_pmu, irq_handler_t handler)
+{
+ int i;
+ struct platform_device *pmu_device = cci_pmu->plat_device;
+
+ if (unlikely(!pmu_device))
+ return -ENODEV;
+
+ if (cci_pmu->nr_irqs < 1) {
+ dev_err(&pmu_device->dev, "no irqs for CCI PMUs defined\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Register all available CCI PMU interrupts. In the interrupt handler
+ * we iterate over the counters checking for interrupt source (the
+ * overflowing counter) and clear it.
+ *
+ * This should allow handling of non-unique interrupt for the counters.
+ */
+ for (i = 0; i < cci_pmu->nr_irqs; i++) {
+ int err = request_irq(cci_pmu->irqs[i], handler, IRQF_SHARED,
+ "arm-cci-pmu", cci_pmu);
+ if (err) {
+ dev_err(&pmu_device->dev, "unable to request IRQ%d for ARM CCI PMU counters\n",
+ cci_pmu->irqs[i]);
+ return err;
+ }
+
+ set_bit(i, &cci_pmu->active_irqs);
+ }
+
+ return 0;
+}
+
+static void pmu_free_irq(struct cci_pmu *cci_pmu)
+{
+ int i;
+
+ for (i = 0; i < cci_pmu->nr_irqs; i++) {
+ if (!test_and_clear_bit(i, &cci_pmu->active_irqs))
+ continue;
+
+ free_irq(cci_pmu->irqs[i], cci_pmu);
+ }
+}
+
+static u32 pmu_read_counter(struct perf_event *event)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ struct hw_perf_event *hw_counter = &event->hw;
+ int idx = hw_counter->idx;
+ u32 value;
+
+ if (unlikely(!pmu_is_valid_counter(cci_pmu, idx))) {
+ dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
+ return 0;
+ }
+ value = pmu_read_register(cci_pmu, idx, CCI_PMU_CNTR);
+
+ return value;
+}
+
+static void pmu_write_counter(struct cci_pmu *cci_pmu, u32 value, int idx)
+{
+ pmu_write_register(cci_pmu, value, idx, CCI_PMU_CNTR);
+}
+
+static void __pmu_write_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
+{
+ int i;
+ struct cci_pmu_hw_events *cci_hw = &cci_pmu->hw_events;
+
+ for_each_set_bit(i, mask, cci_pmu->num_cntrs) {
+ struct perf_event *event = cci_hw->events[i];
+
+ if (WARN_ON(!event))
+ continue;
+ pmu_write_counter(cci_pmu, local64_read(&event->hw.prev_count), i);
+ }
+}
+
+static void pmu_write_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
+{
+ if (cci_pmu->model->write_counters)
+ cci_pmu->model->write_counters(cci_pmu, mask);
+ else
+ __pmu_write_counters(cci_pmu, mask);
+}
+
+#ifdef CONFIG_ARM_CCI5xx_PMU
+
+/*
+ * CCI-500/CCI-550 has advanced power saving policies, which could gate the
+ * clocks to the PMU counters, which makes the writes to them ineffective.
+ * The only way to write to those counters is when the global counters
+ * are enabled and the particular counter is enabled.
+ *
+ * So we do the following :
+ *
+ * 1) Disable all the PMU counters, saving their current state
+ * 2) Enable the global PMU profiling, now that all counters are
+ * disabled.
+ *
+ * For each counter to be programmed, repeat steps 3-7:
+ *
+ * 3) Write an invalid event code to the event control register for the
+ counter, so that the counters are not modified.
+ * 4) Enable the counter control for the counter.
+ * 5) Set the counter value
+ * 6) Disable the counter
+ * 7) Restore the event in the target counter
+ *
+ * 8) Disable the global PMU.
+ * 9) Restore the status of the rest of the counters.
+ *
+ * We choose an event which for CCI-5xx is guaranteed not to count.
+ * We use the highest possible event code (0x1f) for the master interface 0.
+ */
+#define CCI5xx_INVALID_EVENT ((CCI5xx_PORT_M0 << CCI5xx_PMU_EVENT_SOURCE_SHIFT) | \
+ (CCI5xx_PMU_EVENT_CODE_MASK << CCI5xx_PMU_EVENT_CODE_SHIFT))
+static void cci5xx_pmu_write_counters(struct cci_pmu *cci_pmu, unsigned long *mask)
+{
+ int i;
+ DECLARE_BITMAP(saved_mask, cci_pmu->num_cntrs);
+
+ bitmap_zero(saved_mask, cci_pmu->num_cntrs);
+ pmu_save_counters(cci_pmu, saved_mask);
+
+ /*
+ * Now that all the counters are disabled, we can safely turn the PMU on,
+ * without syncing the status of the counters
+ */
+ __cci_pmu_enable_nosync(cci_pmu);
+
+ for_each_set_bit(i, mask, cci_pmu->num_cntrs) {
+ struct perf_event *event = cci_pmu->hw_events.events[i];
+
+ if (WARN_ON(!event))
+ continue;
+
+ pmu_set_event(cci_pmu, i, CCI5xx_INVALID_EVENT);
+ pmu_enable_counter(cci_pmu, i);
+ pmu_write_counter(cci_pmu, local64_read(&event->hw.prev_count), i);
+ pmu_disable_counter(cci_pmu, i);
+ pmu_set_event(cci_pmu, i, event->hw.config_base);
+ }
+
+ __cci_pmu_disable(cci_pmu);
+
+ pmu_restore_counters(cci_pmu, saved_mask);
+}
+
+#endif /* CONFIG_ARM_CCI5xx_PMU */
+
+static u64 pmu_event_update(struct perf_event *event)
+{
+ struct hw_perf_event *hwc = &event->hw;
+ u64 delta, prev_raw_count, new_raw_count;
+
+ do {
+ prev_raw_count = local64_read(&hwc->prev_count);
+ new_raw_count = pmu_read_counter(event);
+ } while (local64_cmpxchg(&hwc->prev_count, prev_raw_count,
+ new_raw_count) != prev_raw_count);
+
+ delta = (new_raw_count - prev_raw_count) & CCI_PMU_CNTR_MASK;
+
+ local64_add(delta, &event->count);
+
+ return new_raw_count;
+}
+
+static void pmu_read(struct perf_event *event)
+{
+ pmu_event_update(event);
+}
+
+static void pmu_event_set_period(struct perf_event *event)
+{
+ struct hw_perf_event *hwc = &event->hw;
+ /*
+ * The CCI PMU counters have a period of 2^32. To account for the
+ * possiblity of extreme interrupt latency we program for a period of
+ * half that. Hopefully we can handle the interrupt before another 2^31
+ * events occur and the counter overtakes its previous value.
+ */
+ u64 val = 1ULL << 31;
+ local64_set(&hwc->prev_count, val);
+
+ /*
+ * CCI PMU uses PERF_HES_ARCH to keep track of the counters, whose
+ * values needs to be sync-ed with the s/w state before the PMU is
+ * enabled.
+ * Mark this counter for sync.
+ */
+ hwc->state |= PERF_HES_ARCH;
+}
+
+static irqreturn_t pmu_handle_irq(int irq_num, void *dev)
+{
+ unsigned long flags;
+ struct cci_pmu *cci_pmu = dev;
+ struct cci_pmu_hw_events *events = &cci_pmu->hw_events;
+ int idx, handled = IRQ_NONE;
+
+ raw_spin_lock_irqsave(&events->pmu_lock, flags);
+
+ /* Disable the PMU while we walk through the counters */
+ __cci_pmu_disable(cci_pmu);
+ /*
+ * Iterate over counters and update the corresponding perf events.
+ * This should work regardless of whether we have per-counter overflow
+ * interrupt or a combined overflow interrupt.
+ */
+ for (idx = 0; idx <= CCI_PMU_CNTR_LAST(cci_pmu); idx++) {
+ struct perf_event *event = events->events[idx];
+
+ if (!event)
+ continue;
+
+ /* Did this counter overflow? */
+ if (!(pmu_read_register(cci_pmu, idx, CCI_PMU_OVRFLW) &
+ CCI_PMU_OVRFLW_FLAG))
+ continue;
+
+ pmu_write_register(cci_pmu, CCI_PMU_OVRFLW_FLAG, idx,
+ CCI_PMU_OVRFLW);
+
+ pmu_event_update(event);
+ pmu_event_set_period(event);
+ handled = IRQ_HANDLED;
+ }
+
+ /* Enable the PMU and sync possibly overflowed counters */
+ __cci_pmu_enable_sync(cci_pmu);
+ raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
+
+ return IRQ_RETVAL(handled);
+}
+
+static int cci_pmu_get_hw(struct cci_pmu *cci_pmu)
+{
+ int ret = pmu_request_irq(cci_pmu, pmu_handle_irq);
+ if (ret) {
+ pmu_free_irq(cci_pmu);
+ return ret;
+ }
+ return 0;
+}
+
+static void cci_pmu_put_hw(struct cci_pmu *cci_pmu)
+{
+ pmu_free_irq(cci_pmu);
+}
+
+static void hw_perf_event_destroy(struct perf_event *event)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ atomic_t *active_events = &cci_pmu->active_events;
+ struct mutex *reserve_mutex = &cci_pmu->reserve_mutex;
+
+ if (atomic_dec_and_mutex_lock(active_events, reserve_mutex)) {
+ cci_pmu_put_hw(cci_pmu);
+ mutex_unlock(reserve_mutex);
+ }
+}
+
+static void cci_pmu_enable(struct pmu *pmu)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(pmu);
+ struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
+ int enabled = bitmap_weight(hw_events->used_mask, cci_pmu->num_cntrs);
+ unsigned long flags;
+
+ if (!enabled)
+ return;
+
+ raw_spin_lock_irqsave(&hw_events->pmu_lock, flags);
+ __cci_pmu_enable_sync(cci_pmu);
+ raw_spin_unlock_irqrestore(&hw_events->pmu_lock, flags);
+
+}
+
+static void cci_pmu_disable(struct pmu *pmu)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(pmu);
+ struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&hw_events->pmu_lock, flags);
+ __cci_pmu_disable(cci_pmu);
+ raw_spin_unlock_irqrestore(&hw_events->pmu_lock, flags);
+}
+
+/*
+ * Check if the idx represents a non-programmable counter.
+ * All the fixed event counters are mapped before the programmable
+ * counters.
+ */
+static bool pmu_fixed_hw_idx(struct cci_pmu *cci_pmu, int idx)
+{
+ return (idx >= 0) && (idx < cci_pmu->model->fixed_hw_cntrs);
+}
+
+static void cci_pmu_start(struct perf_event *event, int pmu_flags)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
+ struct hw_perf_event *hwc = &event->hw;
+ int idx = hwc->idx;
+ unsigned long flags;
+
+ /*
+ * To handle interrupt latency, we always reprogram the period
+ * regardlesss of PERF_EF_RELOAD.
+ */
+ if (pmu_flags & PERF_EF_RELOAD)
+ WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE));
+
+ hwc->state = 0;
+
+ if (unlikely(!pmu_is_valid_counter(cci_pmu, idx))) {
+ dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
+ return;
+ }
+
+ raw_spin_lock_irqsave(&hw_events->pmu_lock, flags);
+
+ /* Configure the counter unless you are counting a fixed event */
+ if (!pmu_fixed_hw_idx(cci_pmu, idx))
+ pmu_set_event(cci_pmu, idx, hwc->config_base);
+
+ pmu_event_set_period(event);
+ pmu_enable_counter(cci_pmu, idx);
+
+ raw_spin_unlock_irqrestore(&hw_events->pmu_lock, flags);
+}
+
+static void cci_pmu_stop(struct perf_event *event, int pmu_flags)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ struct hw_perf_event *hwc = &event->hw;
+ int idx = hwc->idx;
+
+ if (hwc->state & PERF_HES_STOPPED)
+ return;
+
+ if (unlikely(!pmu_is_valid_counter(cci_pmu, idx))) {
+ dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
+ return;
+ }
+
+ /*
+ * We always reprogram the counter, so ignore PERF_EF_UPDATE. See
+ * cci_pmu_start()
+ */
+ pmu_disable_counter(cci_pmu, idx);
+ pmu_event_update(event);
+ hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE;
+}
+
+static int cci_pmu_add(struct perf_event *event, int flags)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
+ struct hw_perf_event *hwc = &event->hw;
+ int idx;
+ int err = 0;
+
+ perf_pmu_disable(event->pmu);
+
+ /* If we don't have a space for the counter then finish early. */
+ idx = pmu_get_event_idx(hw_events, event);
+ if (idx < 0) {
+ err = idx;
+ goto out;
+ }
+
+ event->hw.idx = idx;
+ hw_events->events[idx] = event;
+
+ hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE;
+ if (flags & PERF_EF_START)
+ cci_pmu_start(event, PERF_EF_RELOAD);
+
+ /* Propagate our changes to the userspace mapping. */
+ perf_event_update_userpage(event);
+
+out:
+ perf_pmu_enable(event->pmu);
+ return err;
+}
+
+static void cci_pmu_del(struct perf_event *event, int flags)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ struct cci_pmu_hw_events *hw_events = &cci_pmu->hw_events;
+ struct hw_perf_event *hwc = &event->hw;
+ int idx = hwc->idx;
+
+ cci_pmu_stop(event, PERF_EF_UPDATE);
+ hw_events->events[idx] = NULL;
+ clear_bit(idx, hw_events->used_mask);
+
+ perf_event_update_userpage(event);
+}
+
+static int validate_event(struct pmu *cci_pmu,
+ struct cci_pmu_hw_events *hw_events,
+ struct perf_event *event)
+{
+ if (is_software_event(event))
+ return 1;
+
+ /*
+ * Reject groups spanning multiple HW PMUs (e.g. CPU + CCI). The
+ * core perf code won't check that the pmu->ctx == leader->ctx
+ * until after pmu->event_init(event).
+ */
+ if (event->pmu != cci_pmu)
+ return 0;
+
+ if (event->state < PERF_EVENT_STATE_OFF)
+ return 1;
+
+ if (event->state == PERF_EVENT_STATE_OFF && !event->attr.enable_on_exec)
+ return 1;
+
+ return pmu_get_event_idx(hw_events, event) >= 0;
+}
+
+static int validate_group(struct perf_event *event)
+{
+ struct perf_event *sibling, *leader = event->group_leader;
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ unsigned long mask[BITS_TO_LONGS(cci_pmu->num_cntrs)];
+ struct cci_pmu_hw_events fake_pmu = {
+ /*
+ * Initialise the fake PMU. We only need to populate the
+ * used_mask for the purposes of validation.
+ */
+ .used_mask = mask,
+ };
+ memset(mask, 0, BITS_TO_LONGS(cci_pmu->num_cntrs) * sizeof(unsigned long));
+
+ if (!validate_event(event->pmu, &fake_pmu, leader))
+ return -EINVAL;
+
+ for_each_sibling_event(sibling, leader) {
+ if (!validate_event(event->pmu, &fake_pmu, sibling))
+ return -EINVAL;
+ }
+
+ if (!validate_event(event->pmu, &fake_pmu, event))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int __hw_perf_event_init(struct perf_event *event)
+{
+ struct hw_perf_event *hwc = &event->hw;
+ int mapping;
+
+ mapping = pmu_map_event(event);
+
+ if (mapping < 0) {
+ pr_debug("event %x:%llx not supported\n", event->attr.type,
+ event->attr.config);
+ return mapping;
+ }
+
+ /*
+ * We don't assign an index until we actually place the event onto
+ * hardware. Use -1 to signify that we haven't decided where to put it
+ * yet.
+ */
+ hwc->idx = -1;
+ hwc->config_base = 0;
+ hwc->config = 0;
+ hwc->event_base = 0;
+
+ /*
+ * Store the event encoding into the config_base field.
+ */
+ hwc->config_base |= (unsigned long)mapping;
+
+ /*
+ * Limit the sample_period to half of the counter width. That way, the
+ * new counter value is far less likely to overtake the previous one
+ * unless you have some serious IRQ latency issues.
+ */
+ hwc->sample_period = CCI_PMU_CNTR_MASK >> 1;
+ hwc->last_period = hwc->sample_period;
+ local64_set(&hwc->period_left, hwc->sample_period);
+
+ if (event->group_leader != event) {
+ if (validate_group(event) != 0)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cci_pmu_event_init(struct perf_event *event)
+{
+ struct cci_pmu *cci_pmu = to_cci_pmu(event->pmu);
+ atomic_t *active_events = &cci_pmu->active_events;
+ int err = 0;
+
+ if (event->attr.type != event->pmu->type)
+ return -ENOENT;
+
+ /* Shared by all CPUs, no meaningful state to sample */
+ if (is_sampling_event(event) || event->attach_state & PERF_ATTACH_TASK)
+ return -EOPNOTSUPP;
+
+ /* We have no filtering of any kind */
+ 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;
+
+ /*
+ * Following the example set by other "uncore" PMUs, we accept any CPU
+ * and rewrite its affinity dynamically rather than having perf core
+ * handle cpu == -1 and pid == -1 for this case.
+ *
+ * The perf core will pin online CPUs for the duration of this call and
+ * the event being installed into its context, so the PMU's CPU can't
+ * change under our feet.
+ */
+ if (event->cpu < 0)
+ return -EINVAL;
+ event->cpu = cci_pmu->cpu;
+
+ event->destroy = hw_perf_event_destroy;
+ if (!atomic_inc_not_zero(active_events)) {
+ mutex_lock(&cci_pmu->reserve_mutex);
+ if (atomic_read(active_events) == 0)
+ err = cci_pmu_get_hw(cci_pmu);
+ if (!err)
+ atomic_inc(active_events);
+ mutex_unlock(&cci_pmu->reserve_mutex);
+ }
+ if (err)
+ return err;
+
+ err = __hw_perf_event_init(event);
+ if (err)
+ hw_perf_event_destroy(event);
+
+ return err;
+}
+
+static ssize_t pmu_cpumask_attr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pmu *pmu = dev_get_drvdata(dev);
+ struct cci_pmu *cci_pmu = to_cci_pmu(pmu);
+
+ return cpumap_print_to_pagebuf(true, buf, cpumask_of(cci_pmu->cpu));
+}
+
+static struct device_attribute pmu_cpumask_attr =
+ __ATTR(cpumask, S_IRUGO, pmu_cpumask_attr_show, NULL);
+
+static struct attribute *pmu_attrs[] = {
+ &pmu_cpumask_attr.attr,
+ NULL,
+};
+
+static struct attribute_group pmu_attr_group = {
+ .attrs = pmu_attrs,
+};
+
+static struct attribute_group pmu_format_attr_group = {
+ .name = "format",
+ .attrs = NULL, /* Filled in cci_pmu_init_attrs */
+};
+
+static struct attribute_group pmu_event_attr_group = {
+ .name = "events",
+ .attrs = NULL, /* Filled in cci_pmu_init_attrs */
+};
+
+static const struct attribute_group *pmu_attr_groups[] = {
+ &pmu_attr_group,
+ &pmu_format_attr_group,
+ &pmu_event_attr_group,
+ NULL
+};
+
+static int cci_pmu_init(struct cci_pmu *cci_pmu, struct platform_device *pdev)
+{
+ const struct cci_pmu_model *model = cci_pmu->model;
+ char *name = model->name;
+ u32 num_cntrs;
+
+ pmu_event_attr_group.attrs = model->event_attrs;
+ pmu_format_attr_group.attrs = model->format_attrs;
+
+ cci_pmu->pmu = (struct pmu) {
+ .name = cci_pmu->model->name,
+ .task_ctx_nr = perf_invalid_context,
+ .pmu_enable = cci_pmu_enable,
+ .pmu_disable = cci_pmu_disable,
+ .event_init = cci_pmu_event_init,
+ .add = cci_pmu_add,
+ .del = cci_pmu_del,
+ .start = cci_pmu_start,
+ .stop = cci_pmu_stop,
+ .read = pmu_read,
+ .attr_groups = pmu_attr_groups,
+ };
+
+ cci_pmu->plat_device = pdev;
+ num_cntrs = pmu_get_max_counters(cci_pmu);
+ if (num_cntrs > cci_pmu->model->num_hw_cntrs) {
+ dev_warn(&pdev->dev,
+ "PMU implements more counters(%d) than supported by"
+ " the model(%d), truncated.",
+ num_cntrs, cci_pmu->model->num_hw_cntrs);
+ num_cntrs = cci_pmu->model->num_hw_cntrs;
+ }
+ cci_pmu->num_cntrs = num_cntrs + cci_pmu->model->fixed_hw_cntrs;
+
+ return perf_pmu_register(&cci_pmu->pmu, name, -1);
+}
+
+static int cci_pmu_offline_cpu(unsigned int cpu)
+{
+ int target;
+
+ if (!g_cci_pmu || cpu != g_cci_pmu->cpu)
+ return 0;
+
+ target = cpumask_any_but(cpu_online_mask, cpu);
+ if (target >= nr_cpu_ids)
+ return 0;
+
+ perf_pmu_migrate_context(&g_cci_pmu->pmu, cpu, target);
+ g_cci_pmu->cpu = target;
+ return 0;
+}
+
+static struct cci_pmu_model cci_pmu_models[] = {
+#ifdef CONFIG_ARM_CCI400_PMU
+ [CCI400_R0] = {
+ .name = "CCI_400",
+ .fixed_hw_cntrs = 1, /* Cycle counter */
+ .num_hw_cntrs = 4,
+ .cntr_size = SZ_4K,
+ .format_attrs = cci400_pmu_format_attrs,
+ .event_attrs = cci400_r0_pmu_event_attrs,
+ .event_ranges = {
+ [CCI_IF_SLAVE] = {
+ CCI400_R0_SLAVE_PORT_MIN_EV,
+ CCI400_R0_SLAVE_PORT_MAX_EV,
+ },
+ [CCI_IF_MASTER] = {
+ CCI400_R0_MASTER_PORT_MIN_EV,
+ CCI400_R0_MASTER_PORT_MAX_EV,
+ },
+ },
+ .validate_hw_event = cci400_validate_hw_event,
+ .get_event_idx = cci400_get_event_idx,
+ },
+ [CCI400_R1] = {
+ .name = "CCI_400_r1",
+ .fixed_hw_cntrs = 1, /* Cycle counter */
+ .num_hw_cntrs = 4,
+ .cntr_size = SZ_4K,
+ .format_attrs = cci400_pmu_format_attrs,
+ .event_attrs = cci400_r1_pmu_event_attrs,
+ .event_ranges = {
+ [CCI_IF_SLAVE] = {
+ CCI400_R1_SLAVE_PORT_MIN_EV,
+ CCI400_R1_SLAVE_PORT_MAX_EV,
+ },
+ [CCI_IF_MASTER] = {
+ CCI400_R1_MASTER_PORT_MIN_EV,
+ CCI400_R1_MASTER_PORT_MAX_EV,
+ },
+ },
+ .validate_hw_event = cci400_validate_hw_event,
+ .get_event_idx = cci400_get_event_idx,
+ },
+#endif
+#ifdef CONFIG_ARM_CCI5xx_PMU
+ [CCI500_R0] = {
+ .name = "CCI_500",
+ .fixed_hw_cntrs = 0,
+ .num_hw_cntrs = 8,
+ .cntr_size = SZ_64K,
+ .format_attrs = cci5xx_pmu_format_attrs,
+ .event_attrs = cci5xx_pmu_event_attrs,
+ .event_ranges = {
+ [CCI_IF_SLAVE] = {
+ CCI5xx_SLAVE_PORT_MIN_EV,
+ CCI5xx_SLAVE_PORT_MAX_EV,
+ },
+ [CCI_IF_MASTER] = {
+ CCI5xx_MASTER_PORT_MIN_EV,
+ CCI5xx_MASTER_PORT_MAX_EV,
+ },
+ [CCI_IF_GLOBAL] = {
+ CCI5xx_GLOBAL_PORT_MIN_EV,
+ CCI5xx_GLOBAL_PORT_MAX_EV,
+ },
+ },
+ .validate_hw_event = cci500_validate_hw_event,
+ .write_counters = cci5xx_pmu_write_counters,
+ },
+ [CCI550_R0] = {
+ .name = "CCI_550",
+ .fixed_hw_cntrs = 0,
+ .num_hw_cntrs = 8,
+ .cntr_size = SZ_64K,
+ .format_attrs = cci5xx_pmu_format_attrs,
+ .event_attrs = cci5xx_pmu_event_attrs,
+ .event_ranges = {
+ [CCI_IF_SLAVE] = {
+ CCI5xx_SLAVE_PORT_MIN_EV,
+ CCI5xx_SLAVE_PORT_MAX_EV,
+ },
+ [CCI_IF_MASTER] = {
+ CCI5xx_MASTER_PORT_MIN_EV,
+ CCI5xx_MASTER_PORT_MAX_EV,
+ },
+ [CCI_IF_GLOBAL] = {
+ CCI5xx_GLOBAL_PORT_MIN_EV,
+ CCI5xx_GLOBAL_PORT_MAX_EV,
+ },
+ },
+ .validate_hw_event = cci550_validate_hw_event,
+ .write_counters = cci5xx_pmu_write_counters,
+ },
+#endif
+};
+
+static const struct of_device_id arm_cci_pmu_matches[] = {
+#ifdef CONFIG_ARM_CCI400_PMU
+ {
+ .compatible = "arm,cci-400-pmu",
+ .data = NULL,
+ },
+ {
+ .compatible = "arm,cci-400-pmu,r0",
+ .data = &cci_pmu_models[CCI400_R0],
+ },
+ {
+ .compatible = "arm,cci-400-pmu,r1",
+ .data = &cci_pmu_models[CCI400_R1],
+ },
+#endif
+#ifdef CONFIG_ARM_CCI5xx_PMU
+ {
+ .compatible = "arm,cci-500-pmu,r0",
+ .data = &cci_pmu_models[CCI500_R0],
+ },
+ {
+ .compatible = "arm,cci-550-pmu,r0",
+ .data = &cci_pmu_models[CCI550_R0],
+ },
+#endif
+ {},
+};
+
+static bool is_duplicate_irq(int irq, int *irqs, int nr_irqs)
+{
+ int i;
+
+ for (i = 0; i < nr_irqs; i++)
+ if (irq == irqs[i])
+ return true;
+
+ return false;
+}
+
+static struct cci_pmu *cci_pmu_alloc(struct device *dev)
+{
+ struct cci_pmu *cci_pmu;
+ const struct cci_pmu_model *model;
+
+ /*
+ * All allocations are devm_* hence we don't have to free
+ * them explicitly on an error, as it would end up in driver
+ * detach.
+ */
+ cci_pmu = devm_kzalloc(dev, sizeof(*cci_pmu), GFP_KERNEL);
+ if (!cci_pmu)
+ return ERR_PTR(-ENOMEM);
+
+ cci_pmu->ctrl_base = *(void __iomem **)dev->platform_data;
+
+ model = of_device_get_match_data(dev);
+ if (!model) {
+ dev_warn(dev,
+ "DEPRECATED compatible property, requires secure access to CCI registers");
+ model = probe_cci_model(cci_pmu);
+ }
+ if (!model) {
+ dev_warn(dev, "CCI PMU version not supported\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ cci_pmu->model = model;
+ cci_pmu->irqs = devm_kcalloc(dev, CCI_PMU_MAX_HW_CNTRS(model),
+ sizeof(*cci_pmu->irqs), GFP_KERNEL);
+ if (!cci_pmu->irqs)
+ return ERR_PTR(-ENOMEM);
+ cci_pmu->hw_events.events = devm_kcalloc(dev,
+ CCI_PMU_MAX_HW_CNTRS(model),
+ sizeof(*cci_pmu->hw_events.events),
+ GFP_KERNEL);
+ if (!cci_pmu->hw_events.events)
+ return ERR_PTR(-ENOMEM);
+ cci_pmu->hw_events.used_mask = devm_kcalloc(dev,
+ BITS_TO_LONGS(CCI_PMU_MAX_HW_CNTRS(model)),
+ sizeof(*cci_pmu->hw_events.used_mask),
+ GFP_KERNEL);
+ if (!cci_pmu->hw_events.used_mask)
+ return ERR_PTR(-ENOMEM);
+
+ return cci_pmu;
+}
+
+static int cci_pmu_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct cci_pmu *cci_pmu;
+ int i, ret, irq;
+
+ cci_pmu = cci_pmu_alloc(&pdev->dev);
+ if (IS_ERR(cci_pmu))
+ return PTR_ERR(cci_pmu);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ cci_pmu->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(cci_pmu->base))
+ return -ENOMEM;
+
+ /*
+ * CCI PMU has one overflow interrupt per counter; but some may be tied
+ * together to a common interrupt.
+ */
+ cci_pmu->nr_irqs = 0;
+ for (i = 0; i < CCI_PMU_MAX_HW_CNTRS(cci_pmu->model); i++) {
+ irq = platform_get_irq(pdev, i);
+ if (irq < 0)
+ break;
+
+ if (is_duplicate_irq(irq, cci_pmu->irqs, cci_pmu->nr_irqs))
+ continue;
+
+ cci_pmu->irqs[cci_pmu->nr_irqs++] = irq;
+ }
+
+ /*
+ * Ensure that the device tree has as many interrupts as the number
+ * of counters.
+ */
+ if (i < CCI_PMU_MAX_HW_CNTRS(cci_pmu->model)) {
+ dev_warn(&pdev->dev, "In-correct number of interrupts: %d, should be %d\n",
+ i, CCI_PMU_MAX_HW_CNTRS(cci_pmu->model));
+ return -EINVAL;
+ }
+
+ raw_spin_lock_init(&cci_pmu->hw_events.pmu_lock);
+ mutex_init(&cci_pmu->reserve_mutex);
+ atomic_set(&cci_pmu->active_events, 0);
+ cci_pmu->cpu = get_cpu();
+
+ ret = cci_pmu_init(cci_pmu, pdev);
+ if (ret) {
+ put_cpu();
+ return ret;
+ }
+
+ cpuhp_setup_state_nocalls(CPUHP_AP_PERF_ARM_CCI_ONLINE,
+ "perf/arm/cci:online", NULL,
+ cci_pmu_offline_cpu);
+ put_cpu();
+ g_cci_pmu = cci_pmu;
+ pr_info("ARM %s PMU driver probed", cci_pmu->model->name);
+ return 0;
+}
+
+static struct platform_driver cci_pmu_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = arm_cci_pmu_matches,
+ },
+ .probe = cci_pmu_probe,
+};
+
+builtin_platform_driver(cci_pmu_driver);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ARM CCI PMU support");
diff --git a/drivers/bus/arm-ccn.c b/drivers/perf/arm-ccn.c
index 65b7e4042ece..65b7e4042ece 100644
--- a/drivers/bus/arm-ccn.c
+++ b/drivers/perf/arm-ccn.c
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index 7fc77696bb1e..c0b292be1b72 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -49,6 +49,7 @@ config RESET_HSDK
config RESET_IMX7
bool "i.MX7 Reset Driver" if COMPILE_TEST
+ depends on HAS_IOMEM
default SOC_IMX7D
select MFD_SYSCON
help
@@ -83,14 +84,24 @@ config RESET_PISTACHIO
config RESET_SIMPLE
bool "Simple Reset Controller Driver" if COMPILE_TEST
- default ARCH_SOCFPGA || ARCH_STM32 || ARCH_STRATIX10 || ARCH_SUNXI || ARCH_ZX
+ default ARCH_SOCFPGA || ARCH_STM32 || ARCH_STRATIX10 || ARCH_SUNXI || ARCH_ZX || ARCH_ASPEED
help
This enables a simple reset controller driver for reset lines that
that can be asserted and deasserted by toggling bits in a contiguous,
exclusive register space.
- Currently this driver supports Altera SoCFPGAs, the RCC reset
- controller in STM32 MCUs, Allwinner SoCs, and ZTE's zx2967 family.
+ Currently this driver supports:
+ - Altera SoCFPGAs
+ - ASPEED BMC SoCs
+ - RCC reset controller in STM32 MCUs
+ - Allwinner SoCs
+ - ZTE's zx2967 family
+
+config RESET_STM32MP157
+ bool "STM32MP157 Reset Driver" if COMPILE_TEST
+ default MACH_STM32MP157
+ help
+ This enables the RCC reset controller driver for STM32 MPUs.
config RESET_SUNXI
bool "Allwinner SoCs Reset Driver" if COMPILE_TEST && !ARCH_SUNXI
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 132c24f5ddb5..c1261dcfe9ad 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_RESET_MESON) += reset-meson.o
obj-$(CONFIG_RESET_OXNAS) += reset-oxnas.o
obj-$(CONFIG_RESET_PISTACHIO) += reset-pistachio.o
obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o
+obj-$(CONFIG_RESET_STM32MP157) += reset-stm32mp1.o
obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o
obj-$(CONFIG_RESET_TI_SCI) += reset-ti-sci.o
obj-$(CONFIG_RESET_TI_SYSCON) += reset-ti-syscon.o
diff --git a/drivers/reset/core.c b/drivers/reset/core.c
index da4292e9de97..6488292e129c 100644
--- a/drivers/reset/core.c
+++ b/drivers/reset/core.c
@@ -23,6 +23,9 @@
static DEFINE_MUTEX(reset_list_mutex);
static LIST_HEAD(reset_controller_list);
+static DEFINE_MUTEX(reset_lookup_mutex);
+static LIST_HEAD(reset_lookup_list);
+
/**
* struct reset_control - a reset control
* @rcdev: a pointer to the reset controller device
@@ -148,6 +151,33 @@ int devm_reset_controller_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_reset_controller_register);
+/**
+ * reset_controller_add_lookup - register a set of lookup entries
+ * @lookup: array of reset lookup entries
+ * @num_entries: number of entries in the lookup array
+ */
+void reset_controller_add_lookup(struct reset_control_lookup *lookup,
+ unsigned int num_entries)
+{
+ struct reset_control_lookup *entry;
+ unsigned int i;
+
+ mutex_lock(&reset_lookup_mutex);
+ for (i = 0; i < num_entries; i++) {
+ entry = &lookup[i];
+
+ if (!entry->dev_id || !entry->provider) {
+ pr_warn("%s(): reset lookup entry badly specified, skipping\n",
+ __func__);
+ continue;
+ }
+
+ list_add_tail(&entry->list, &reset_lookup_list);
+ }
+ mutex_unlock(&reset_lookup_mutex);
+}
+EXPORT_SYMBOL_GPL(reset_controller_add_lookup);
+
static inline struct reset_control_array *
rstc_to_array(struct reset_control *rstc) {
return container_of(rstc, struct reset_control_array, base);
@@ -493,6 +523,70 @@ struct reset_control *__of_reset_control_get(struct device_node *node,
}
EXPORT_SYMBOL_GPL(__of_reset_control_get);
+static struct reset_controller_dev *
+__reset_controller_by_name(const char *name)
+{
+ struct reset_controller_dev *rcdev;
+
+ lockdep_assert_held(&reset_list_mutex);
+
+ list_for_each_entry(rcdev, &reset_controller_list, list) {
+ if (!rcdev->dev)
+ continue;
+
+ if (!strcmp(name, dev_name(rcdev->dev)))
+ return rcdev;
+ }
+
+ return NULL;
+}
+
+static struct reset_control *
+__reset_control_get_from_lookup(struct device *dev, const char *con_id,
+ bool shared, bool optional)
+{
+ const struct reset_control_lookup *lookup;
+ struct reset_controller_dev *rcdev;
+ const char *dev_id = dev_name(dev);
+ struct reset_control *rstc = NULL;
+
+ if (!dev)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&reset_lookup_mutex);
+
+ list_for_each_entry(lookup, &reset_lookup_list, list) {
+ if (strcmp(lookup->dev_id, dev_id))
+ continue;
+
+ if ((!con_id && !lookup->con_id) ||
+ ((con_id && lookup->con_id) &&
+ !strcmp(con_id, lookup->con_id))) {
+ mutex_lock(&reset_list_mutex);
+ rcdev = __reset_controller_by_name(lookup->provider);
+ if (!rcdev) {
+ mutex_unlock(&reset_list_mutex);
+ mutex_unlock(&reset_lookup_mutex);
+ /* Reset provider may not be ready yet. */
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ rstc = __reset_control_get_internal(rcdev,
+ lookup->index,
+ shared);
+ mutex_unlock(&reset_list_mutex);
+ break;
+ }
+ }
+
+ mutex_unlock(&reset_lookup_mutex);
+
+ if (!rstc)
+ return optional ? NULL : ERR_PTR(-ENOENT);
+
+ return rstc;
+}
+
struct reset_control *__reset_control_get(struct device *dev, const char *id,
int index, bool shared, bool optional)
{
@@ -500,7 +594,7 @@ struct reset_control *__reset_control_get(struct device *dev, const char *id,
return __of_reset_control_get(dev->of_node, id, index, shared,
optional);
- return optional ? NULL : ERR_PTR(-EINVAL);
+ return __reset_control_get_from_lookup(dev, id, shared, optional);
}
EXPORT_SYMBOL_GPL(__reset_control_get);
diff --git a/drivers/reset/reset-meson.c b/drivers/reset/reset-meson.c
index 93cbee1ae8ef..5242e0679df7 100644
--- a/drivers/reset/reset-meson.c
+++ b/drivers/reset/reset-meson.c
@@ -124,29 +124,21 @@ static int meson_reset_deassert(struct reset_controller_dev *rcdev,
return meson_reset_level(rcdev, id, false);
}
-static const struct reset_control_ops meson_reset_meson8_ops = {
- .reset = meson_reset_reset,
-};
-
-static const struct reset_control_ops meson_reset_gx_ops = {
+static const struct reset_control_ops meson_reset_ops = {
.reset = meson_reset_reset,
.assert = meson_reset_assert,
.deassert = meson_reset_deassert,
};
static const struct of_device_id meson_reset_dt_ids[] = {
- { .compatible = "amlogic,meson8b-reset",
- .data = &meson_reset_meson8_ops, },
- { .compatible = "amlogic,meson-gxbb-reset",
- .data = &meson_reset_gx_ops, },
- { .compatible = "amlogic,meson-axg-reset",
- .data = &meson_reset_gx_ops, },
+ { .compatible = "amlogic,meson8b-reset" },
+ { .compatible = "amlogic,meson-gxbb-reset" },
+ { .compatible = "amlogic,meson-axg-reset" },
{ /* sentinel */ },
};
static int meson_reset_probe(struct platform_device *pdev)
{
- const struct reset_control_ops *ops;
struct meson_reset *data;
struct resource *res;
@@ -154,10 +146,6 @@ static int meson_reset_probe(struct platform_device *pdev)
if (!data)
return -ENOMEM;
- ops = of_device_get_match_data(&pdev->dev);
- if (!ops)
- return -EINVAL;
-
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
data->reg_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->reg_base))
@@ -169,7 +157,7 @@ static int meson_reset_probe(struct platform_device *pdev)
data->rcdev.owner = THIS_MODULE;
data->rcdev.nr_resets = REG_COUNT * BITS_PER_REG;
- data->rcdev.ops = ops;
+ data->rcdev.ops = &meson_reset_ops;
data->rcdev.of_node = pdev->dev.of_node;
return devm_reset_controller_register(&pdev->dev, &data->rcdev);
diff --git a/drivers/reset/reset-simple.c b/drivers/reset/reset-simple.c
index 2d4f362ef025..f7ce8910a392 100644
--- a/drivers/reset/reset-simple.c
+++ b/drivers/reset/reset-simple.c
@@ -125,6 +125,8 @@ static const struct of_device_id reset_simple_dt_ids[] = {
.data = &reset_simple_active_low },
{ .compatible = "zte,zx296718-reset",
.data = &reset_simple_active_low },
+ { .compatible = "aspeed,ast2400-lpc-reset" },
+ { .compatible = "aspeed,ast2500-lpc-reset" },
{ /* sentinel */ },
};
diff --git a/drivers/reset/reset-stm32mp1.c b/drivers/reset/reset-stm32mp1.c
new file mode 100644
index 000000000000..b221a28041fa
--- /dev/null
+++ b/drivers/reset/reset-stm32mp1.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Gabriel Fernandez <gabriel.fernandez@st.com> for STMicroelectronics.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+
+#define CLR_OFFSET 0x4
+
+struct stm32_reset_data {
+ struct reset_controller_dev rcdev;
+ void __iomem *membase;
+};
+
+static inline struct stm32_reset_data *
+to_stm32_reset_data(struct reset_controller_dev *rcdev)
+{
+ return container_of(rcdev, struct stm32_reset_data, rcdev);
+}
+
+static int stm32_reset_update(struct reset_controller_dev *rcdev,
+ unsigned long id, bool assert)
+{
+ struct stm32_reset_data *data = to_stm32_reset_data(rcdev);
+ int reg_width = sizeof(u32);
+ int bank = id / (reg_width * BITS_PER_BYTE);
+ int offset = id % (reg_width * BITS_PER_BYTE);
+ void __iomem *addr;
+
+ addr = data->membase + (bank * reg_width);
+ if (!assert)
+ addr += CLR_OFFSET;
+
+ writel(BIT(offset), addr);
+
+ return 0;
+}
+
+static int stm32_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return stm32_reset_update(rcdev, id, true);
+}
+
+static int stm32_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return stm32_reset_update(rcdev, id, false);
+}
+
+static int stm32_reset_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct stm32_reset_data *data = to_stm32_reset_data(rcdev);
+ int reg_width = sizeof(u32);
+ int bank = id / (reg_width * BITS_PER_BYTE);
+ int offset = id % (reg_width * BITS_PER_BYTE);
+ u32 reg;
+
+ reg = readl(data->membase + (bank * reg_width));
+
+ return !!(reg & BIT(offset));
+}
+
+static const struct reset_control_ops stm32_reset_ops = {
+ .assert = stm32_reset_assert,
+ .deassert = stm32_reset_deassert,
+ .status = stm32_reset_status,
+};
+
+static const struct of_device_id stm32_reset_dt_ids[] = {
+ { .compatible = "st,stm32mp1-rcc"},
+ { /* sentinel */ },
+};
+
+static int stm32_reset_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct stm32_reset_data *data;
+ void __iomem *membase;
+ struct resource *res;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ membase = devm_ioremap_resource(dev, res);
+ if (IS_ERR(membase))
+ return PTR_ERR(membase);
+
+ data->membase = membase;
+ data->rcdev.owner = THIS_MODULE;
+ data->rcdev.nr_resets = resource_size(res) * BITS_PER_BYTE;
+ data->rcdev.ops = &stm32_reset_ops;
+ data->rcdev.of_node = dev->of_node;
+
+ return devm_reset_controller_register(dev, &data->rcdev);
+}
+
+static struct platform_driver stm32_reset_driver = {
+ .probe = stm32_reset_probe,
+ .driver = {
+ .name = "stm32mp1-reset",
+ .of_match_table = stm32_reset_dt_ids,
+ },
+};
+
+builtin_platform_driver(stm32_reset_driver);
diff --git a/drivers/reset/reset-uniphier.c b/drivers/reset/reset-uniphier.c
index e8bb023ff15e..360e06b20c53 100644
--- a/drivers/reset/reset-uniphier.c
+++ b/drivers/reset/reset-uniphier.c
@@ -63,6 +63,7 @@ static const struct uniphier_reset_data uniphier_pro4_sys_reset_data[] = {
UNIPHIER_RESETX(12, 0x2000, 6), /* GIO (Ether, SATA, USB3) */
UNIPHIER_RESETX(14, 0x2000, 17), /* USB30 */
UNIPHIER_RESETX(15, 0x2004, 17), /* USB31 */
+ UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */
UNIPHIER_RESET_END,
};
@@ -72,6 +73,7 @@ static const struct uniphier_reset_data uniphier_pro5_sys_reset_data[] = {
UNIPHIER_RESETX(12, 0x2000, 6), /* GIO (PCIe, USB3) */
UNIPHIER_RESETX(14, 0x2000, 17), /* USB30 */
UNIPHIER_RESETX(15, 0x2004, 17), /* USB31 */
+ UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */
UNIPHIER_RESET_END,
};
@@ -88,6 +90,7 @@ static const struct uniphier_reset_data uniphier_pxs2_sys_reset_data[] = {
UNIPHIER_RESETX(21, 0x2014, 1), /* USB31-PHY1 */
UNIPHIER_RESETX(28, 0x2014, 12), /* SATA */
UNIPHIER_RESET(29, 0x2014, 8), /* SATA-PHY (active high) */
+ UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */
UNIPHIER_RESET_END,
};
@@ -121,6 +124,8 @@ static const struct uniphier_reset_data uniphier_ld20_sys_reset_data[] = {
static const struct uniphier_reset_data uniphier_pxs3_sys_reset_data[] = {
UNIPHIER_RESETX(2, 0x200c, 0), /* NAND */
UNIPHIER_RESETX(4, 0x200c, 2), /* eMMC */
+ UNIPHIER_RESETX(6, 0x200c, 9), /* Ether0 */
+ UNIPHIER_RESETX(7, 0x200c, 10), /* Ether1 */
UNIPHIER_RESETX(8, 0x200c, 12), /* STDMAC */
UNIPHIER_RESETX(12, 0x200c, 4), /* USB30 link (GIO0) */
UNIPHIER_RESETX(13, 0x200c, 5), /* USB31 link (GIO1) */
diff --git a/drivers/soc/amlogic/meson-gx-pwrc-vpu.c b/drivers/soc/amlogic/meson-gx-pwrc-vpu.c
index 2bdeebc48901..6289965c42e9 100644
--- a/drivers/soc/amlogic/meson-gx-pwrc-vpu.c
+++ b/drivers/soc/amlogic/meson-gx-pwrc-vpu.c
@@ -184,7 +184,8 @@ static int meson_gx_pwrc_vpu_probe(struct platform_device *pdev)
rstc = devm_reset_control_array_get(&pdev->dev, false, false);
if (IS_ERR(rstc)) {
- dev_err(&pdev->dev, "failed to get reset lines\n");
+ if (PTR_ERR(rstc) != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "failed to get reset lines\n");
return PTR_ERR(rstc);
}
@@ -224,7 +225,11 @@ static int meson_gx_pwrc_vpu_probe(struct platform_device *pdev)
static void meson_gx_pwrc_vpu_shutdown(struct platform_device *pdev)
{
- meson_gx_pwrc_vpu_power_off(&vpu_hdmi_pd.genpd);
+ bool powered_off;
+
+ powered_off = meson_gx_pwrc_vpu_get_power(&vpu_hdmi_pd);
+ if (!powered_off)
+ meson_gx_pwrc_vpu_power_off(&vpu_hdmi_pd.genpd);
}
static const struct of_device_id meson_gx_pwrc_vpu_match_table[] = {
diff --git a/drivers/soc/amlogic/meson-gx-socinfo.c b/drivers/soc/amlogic/meson-gx-socinfo.c
index 8bdaa9b43d49..37ea0a1c24c8 100644
--- a/drivers/soc/amlogic/meson-gx-socinfo.c
+++ b/drivers/soc/amlogic/meson-gx-socinfo.c
@@ -33,6 +33,10 @@ static const struct meson_gx_soc_id {
{ "GXL", 0x21 },
{ "GXM", 0x22 },
{ "TXL", 0x23 },
+ { "TXLX", 0x24 },
+ { "AXG", 0x25 },
+ { "GXLX", 0x26 },
+ { "TXHD", 0x27 },
};
static const struct meson_gx_package_id {
@@ -45,9 +49,14 @@ static const struct meson_gx_package_id {
{ "S905M", 0x1f, 0x20 },
{ "S905D", 0x21, 0 },
{ "S905X", 0x21, 0x80 },
+ { "S905W", 0x21, 0xa0 },
{ "S905L", 0x21, 0xc0 },
{ "S905M2", 0x21, 0xe0 },
{ "S912", 0x22, 0 },
+ { "962X", 0x24, 0x10 },
+ { "962E", 0x24, 0x20 },
+ { "A113X", 0x25, 0x37 },
+ { "A113D", 0x25, 0x22 },
};
static inline unsigned int socinfo_to_major(u32 socinfo)
@@ -98,7 +107,7 @@ static const char *socinfo_to_soc_id(u32 socinfo)
return "Unknown";
}
-int __init meson_gx_socinfo_init(void)
+static int __init meson_gx_socinfo_init(void)
{
struct soc_device_attribute *soc_dev_attr;
struct soc_device *soc_dev;
diff --git a/drivers/soc/amlogic/meson-mx-socinfo.c b/drivers/soc/amlogic/meson-mx-socinfo.c
index 7bfff5ff22a2..78f0f1aeca57 100644
--- a/drivers/soc/amlogic/meson-mx-socinfo.c
+++ b/drivers/soc/amlogic/meson-mx-socinfo.c
@@ -104,7 +104,7 @@ static const struct of_device_id meson_mx_socinfo_analog_top_ids[] = {
{ /* sentinel */ }
};
-int __init meson_mx_socinfo_init(void)
+static int __init meson_mx_socinfo_init(void)
{
struct soc_device_attribute *soc_dev_attr;
struct soc_device *soc_dev;
diff --git a/drivers/soc/imx/gpc.c b/drivers/soc/imx/gpc.c
index 750f93197411..c4d35f32af8d 100644
--- a/drivers/soc/imx/gpc.c
+++ b/drivers/soc/imx/gpc.c
@@ -254,6 +254,7 @@ static struct imx_pm_domain imx_gpc_domains[] = {
{
.base = {
.name = "ARM",
+ .flags = GENPD_FLAG_ALWAYS_ON,
},
}, {
.base = {
diff --git a/drivers/soc/mediatek/mtk-scpsys.c b/drivers/soc/mediatek/mtk-scpsys.c
index 435ce5ec648a..d762a46d434f 100644
--- a/drivers/soc/mediatek/mtk-scpsys.c
+++ b/drivers/soc/mediatek/mtk-scpsys.c
@@ -24,6 +24,7 @@
#include <dt-bindings/power/mt2712-power.h>
#include <dt-bindings/power/mt6797-power.h>
#include <dt-bindings/power/mt7622-power.h>
+#include <dt-bindings/power/mt7623a-power.h>
#include <dt-bindings/power/mt8173-power.h>
#define SPM_VDE_PWR_CON 0x0210
@@ -518,7 +519,8 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = {
.name = "conn",
.sta_mask = PWR_STATUS_CONN,
.ctl_offs = SPM_CONN_PWR_CON,
- .bus_prot_mask = 0x0104,
+ .bus_prot_mask = MT2701_TOP_AXI_PROT_EN_CONN_M |
+ MT2701_TOP_AXI_PROT_EN_CONN_S,
.clk_id = {CLK_NONE},
.active_wakeup = true,
},
@@ -528,7 +530,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = {
.ctl_offs = SPM_DIS_PWR_CON,
.sram_pdn_bits = GENMASK(11, 8),
.clk_id = {CLK_MM},
- .bus_prot_mask = 0x0002,
+ .bus_prot_mask = MT2701_TOP_AXI_PROT_EN_MM_M0,
.active_wakeup = true,
},
[MT2701_POWER_DOMAIN_MFG] = {
@@ -664,12 +666,48 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = {
.name = "mfg",
.sta_mask = PWR_STATUS_MFG,
.ctl_offs = SPM_MFG_PWR_CON,
- .sram_pdn_bits = GENMASK(11, 8),
- .sram_pdn_ack_bits = GENMASK(19, 16),
+ .sram_pdn_bits = GENMASK(8, 8),
+ .sram_pdn_ack_bits = GENMASK(16, 16),
.clk_id = {CLK_MFG},
.bus_prot_mask = BIT(14) | BIT(21) | BIT(23),
.active_wakeup = true,
},
+ [MT2712_POWER_DOMAIN_MFG_SC1] = {
+ .name = "mfg_sc1",
+ .sta_mask = BIT(22),
+ .ctl_offs = 0x02c0,
+ .sram_pdn_bits = GENMASK(8, 8),
+ .sram_pdn_ack_bits = GENMASK(16, 16),
+ .clk_id = {CLK_NONE},
+ .active_wakeup = true,
+ },
+ [MT2712_POWER_DOMAIN_MFG_SC2] = {
+ .name = "mfg_sc2",
+ .sta_mask = BIT(23),
+ .ctl_offs = 0x02c4,
+ .sram_pdn_bits = GENMASK(8, 8),
+ .sram_pdn_ack_bits = GENMASK(16, 16),
+ .clk_id = {CLK_NONE},
+ .active_wakeup = true,
+ },
+ [MT2712_POWER_DOMAIN_MFG_SC3] = {
+ .name = "mfg_sc3",
+ .sta_mask = BIT(30),
+ .ctl_offs = 0x01f8,
+ .sram_pdn_bits = GENMASK(8, 8),
+ .sram_pdn_ack_bits = GENMASK(16, 16),
+ .clk_id = {CLK_NONE},
+ .active_wakeup = true,
+ },
+};
+
+static const struct scp_subdomain scp_subdomain_mt2712[] = {
+ {MT2712_POWER_DOMAIN_MM, MT2712_POWER_DOMAIN_VDEC},
+ {MT2712_POWER_DOMAIN_MM, MT2712_POWER_DOMAIN_VENC},
+ {MT2712_POWER_DOMAIN_MM, MT2712_POWER_DOMAIN_ISP},
+ {MT2712_POWER_DOMAIN_MFG, MT2712_POWER_DOMAIN_MFG_SC1},
+ {MT2712_POWER_DOMAIN_MFG_SC1, MT2712_POWER_DOMAIN_MFG_SC2},
+ {MT2712_POWER_DOMAIN_MFG_SC2, MT2712_POWER_DOMAIN_MFG_SC3},
};
/*
@@ -794,6 +832,47 @@ static const struct scp_domain_data scp_domain_data_mt7622[] = {
};
/*
+ * MT7623A power domain support
+ */
+
+static const struct scp_domain_data scp_domain_data_mt7623a[] = {
+ [MT7623A_POWER_DOMAIN_CONN] = {
+ .name = "conn",
+ .sta_mask = PWR_STATUS_CONN,
+ .ctl_offs = SPM_CONN_PWR_CON,
+ .bus_prot_mask = MT2701_TOP_AXI_PROT_EN_CONN_M |
+ MT2701_TOP_AXI_PROT_EN_CONN_S,
+ .clk_id = {CLK_NONE},
+ .active_wakeup = true,
+ },
+ [MT7623A_POWER_DOMAIN_ETH] = {
+ .name = "eth",
+ .sta_mask = PWR_STATUS_ETH,
+ .ctl_offs = SPM_ETH_PWR_CON,
+ .sram_pdn_bits = GENMASK(11, 8),
+ .sram_pdn_ack_bits = GENMASK(15, 12),
+ .clk_id = {CLK_ETHIF},
+ .active_wakeup = true,
+ },
+ [MT7623A_POWER_DOMAIN_HIF] = {
+ .name = "hif",
+ .sta_mask = PWR_STATUS_HIF,
+ .ctl_offs = SPM_HIF_PWR_CON,
+ .sram_pdn_bits = GENMASK(11, 8),
+ .sram_pdn_ack_bits = GENMASK(15, 12),
+ .clk_id = {CLK_ETHIF},
+ .active_wakeup = true,
+ },
+ [MT7623A_POWER_DOMAIN_IFR_MSC] = {
+ .name = "ifr_msc",
+ .sta_mask = PWR_STATUS_IFR_MSC,
+ .ctl_offs = SPM_IFR_MSC_PWR_CON,
+ .clk_id = {CLK_NONE},
+ .active_wakeup = true,
+ },
+};
+
+/*
* MT8173 power domain support
*/
@@ -905,6 +984,8 @@ static const struct scp_soc_data mt2701_data = {
static const struct scp_soc_data mt2712_data = {
.domains = scp_domain_data_mt2712,
.num_domains = ARRAY_SIZE(scp_domain_data_mt2712),
+ .subdomains = scp_subdomain_mt2712,
+ .num_subdomains = ARRAY_SIZE(scp_subdomain_mt2712),
.regs = {
.pwr_sta_offs = SPM_PWR_STATUS,
.pwr_sta2nd_offs = SPM_PWR_STATUS_2ND
@@ -934,6 +1015,16 @@ static const struct scp_soc_data mt7622_data = {
.bus_prot_reg_update = true,
};
+static const struct scp_soc_data mt7623a_data = {
+ .domains = scp_domain_data_mt7623a,
+ .num_domains = ARRAY_SIZE(scp_domain_data_mt7623a),
+ .regs = {
+ .pwr_sta_offs = SPM_PWR_STATUS,
+ .pwr_sta2nd_offs = SPM_PWR_STATUS_2ND
+ },
+ .bus_prot_reg_update = true,
+};
+
static const struct scp_soc_data mt8173_data = {
.domains = scp_domain_data_mt8173,
.num_domains = ARRAY_SIZE(scp_domain_data_mt8173),
@@ -964,6 +1055,9 @@ static const struct of_device_id of_scpsys_match_tbl[] = {
.compatible = "mediatek,mt7622-scpsys",
.data = &mt7622_data,
}, {
+ .compatible = "mediatek,mt7623a-scpsys",
+ .data = &mt7623a_data,
+ }, {
.compatible = "mediatek,mt8173-scpsys",
.data = &mt8173_data,
}, {
@@ -992,7 +1086,7 @@ static int scpsys_probe(struct platform_device *pdev)
pd_data = &scp->pd_data;
- for (i = 0, sd = soc->subdomains ; i < soc->num_subdomains ; i++) {
+ for (i = 0, sd = soc->subdomains; i < soc->num_subdomains; i++, sd++) {
ret = pm_genpd_add_subdomain(pd_data->domains[sd->origin],
pd_data->domains[sd->subdomain]);
if (ret && IS_ENABLED(CONFIG_PM))
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index e050eb83341d..a993d19fa562 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -47,6 +47,7 @@ config QCOM_QMI_HELPERS
config QCOM_RMTFS_MEM
tristate "Qualcomm Remote Filesystem memory driver"
depends on ARCH_QCOM
+ select QCOM_SCM
help
The Qualcomm remote filesystem memory driver is used for allocating
and exposing regions of shared memory with remote processors for the
diff --git a/drivers/soc/qcom/rmtfs_mem.c b/drivers/soc/qcom/rmtfs_mem.c
index 0a43b2e8906f..c8999e38b005 100644
--- a/drivers/soc/qcom/rmtfs_mem.c
+++ b/drivers/soc/qcom/rmtfs_mem.c
@@ -37,6 +37,8 @@ struct qcom_rmtfs_mem {
phys_addr_t size;
unsigned int client_id;
+
+ unsigned int perms;
};
static ssize_t qcom_rmtfs_mem_show(struct device *dev,
@@ -151,9 +153,11 @@ static void qcom_rmtfs_mem_release_device(struct device *dev)
static int qcom_rmtfs_mem_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
+ struct qcom_scm_vmperm perms[2];
struct reserved_mem *rmem;
struct qcom_rmtfs_mem *rmtfs_mem;
u32 client_id;
+ u32 vmid;
int ret;
rmem = of_reserved_mem_lookup(node);
@@ -204,10 +208,31 @@ static int qcom_rmtfs_mem_probe(struct platform_device *pdev)
rmtfs_mem->dev.release = qcom_rmtfs_mem_release_device;
+ ret = of_property_read_u32(node, "qcom,vmid", &vmid);
+ if (ret < 0 && ret != -EINVAL) {
+ dev_err(&pdev->dev, "failed to parse qcom,vmid\n");
+ goto remove_cdev;
+ } else if (!ret) {
+ perms[0].vmid = QCOM_SCM_VMID_HLOS;
+ perms[0].perm = QCOM_SCM_PERM_RW;
+ perms[1].vmid = vmid;
+ perms[1].perm = QCOM_SCM_PERM_RW;
+
+ rmtfs_mem->perms = BIT(QCOM_SCM_VMID_HLOS);
+ ret = qcom_scm_assign_mem(rmtfs_mem->addr, rmtfs_mem->size,
+ &rmtfs_mem->perms, perms, 2);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "assign memory failed\n");
+ goto remove_cdev;
+ }
+ }
+
dev_set_drvdata(&pdev->dev, rmtfs_mem);
return 0;
+remove_cdev:
+ cdev_device_del(&rmtfs_mem->cdev, &rmtfs_mem->dev);
put_device:
put_device(&rmtfs_mem->dev);
@@ -217,6 +242,15 @@ put_device:
static int qcom_rmtfs_mem_remove(struct platform_device *pdev)
{
struct qcom_rmtfs_mem *rmtfs_mem = dev_get_drvdata(&pdev->dev);
+ struct qcom_scm_vmperm perm;
+
+ if (rmtfs_mem->perms) {
+ perm.vmid = QCOM_SCM_VMID_HLOS;
+ perm.perm = QCOM_SCM_PERM_RW;
+
+ qcom_scm_assign_mem(rmtfs_mem->addr, rmtfs_mem->size,
+ &rmtfs_mem->perms, &perm, 1);
+ }
cdev_device_del(&rmtfs_mem->cdev, &rmtfs_mem->dev);
put_device(&rmtfs_mem->dev);
diff --git a/drivers/soc/qcom/wcnss_ctrl.c b/drivers/soc/qcom/wcnss_ctrl.c
index d008e5b82db4..df3ccb30bc2d 100644
--- a/drivers/soc/qcom/wcnss_ctrl.c
+++ b/drivers/soc/qcom/wcnss_ctrl.c
@@ -249,7 +249,7 @@ static int wcnss_download_nv(struct wcnss_ctrl *wcnss, bool *expect_cbc)
/* Increment for next fragment */
req->seq++;
- data += req->hdr.len;
+ data += NV_FRAGMENT_SIZE;
left -= NV_FRAGMENT_SIZE;
} while (left > 0);
diff --git a/drivers/soc/rockchip/grf.c b/drivers/soc/rockchip/grf.c
index 15e71fd6c513..96882ffde67e 100644
--- a/drivers/soc/rockchip/grf.c
+++ b/drivers/soc/rockchip/grf.c
@@ -43,6 +43,28 @@ static const struct rockchip_grf_info rk3036_grf __initconst = {
.num_values = ARRAY_SIZE(rk3036_defaults),
};
+#define RK3128_GRF_SOC_CON0 0x140
+
+static const struct rockchip_grf_value rk3128_defaults[] __initconst = {
+ { "jtag switching", RK3128_GRF_SOC_CON0, HIWORD_UPDATE(0, 1, 8) },
+};
+
+static const struct rockchip_grf_info rk3128_grf __initconst = {
+ .values = rk3128_defaults,
+ .num_values = ARRAY_SIZE(rk3128_defaults),
+};
+
+#define RK3228_GRF_SOC_CON6 0x418
+
+static const struct rockchip_grf_value rk3228_defaults[] __initconst = {
+ { "jtag switching", RK3228_GRF_SOC_CON6, HIWORD_UPDATE(0, 1, 8) },
+};
+
+static const struct rockchip_grf_info rk3228_grf __initconst = {
+ .values = rk3228_defaults,
+ .num_values = ARRAY_SIZE(rk3228_defaults),
+};
+
#define RK3288_GRF_SOC_CON0 0x244
static const struct rockchip_grf_value rk3288_defaults[] __initconst = {
@@ -92,6 +114,12 @@ static const struct of_device_id rockchip_grf_dt_match[] __initconst = {
.compatible = "rockchip,rk3036-grf",
.data = (void *)&rk3036_grf,
}, {
+ .compatible = "rockchip,rk3128-grf",
+ .data = (void *)&rk3128_grf,
+ }, {
+ .compatible = "rockchip,rk3228-grf",
+ .data = (void *)&rk3228_grf,
+ }, {
.compatible = "rockchip,rk3288-grf",
.data = (void *)&rk3288_grf,
}, {
diff --git a/drivers/soc/rockchip/pm_domains.c b/drivers/soc/rockchip/pm_domains.c
index 5c342167b9db..53efc386b1ad 100644
--- a/drivers/soc/rockchip/pm_domains.c
+++ b/drivers/soc/rockchip/pm_domains.c
@@ -67,7 +67,7 @@ struct rockchip_pm_domain {
struct regmap **qos_regmap;
u32 *qos_save_regs[MAX_QOS_REGS_NUM];
int num_clks;
- struct clk *clks[];
+ struct clk_bulk_data *clks;
};
struct rockchip_pmu {
@@ -274,13 +274,18 @@ static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd,
static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
{
- int i;
+ struct rockchip_pmu *pmu = pd->pmu;
+ int ret;
- mutex_lock(&pd->pmu->mutex);
+ mutex_lock(&pmu->mutex);
if (rockchip_pmu_domain_is_on(pd) != power_on) {
- for (i = 0; i < pd->num_clks; i++)
- clk_enable(pd->clks[i]);
+ ret = clk_bulk_enable(pd->num_clks, pd->clks);
+ if (ret < 0) {
+ dev_err(pmu->dev, "failed to enable clocks\n");
+ mutex_unlock(&pmu->mutex);
+ return ret;
+ }
if (!power_on) {
rockchip_pmu_save_qos(pd);
@@ -298,11 +303,10 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
rockchip_pmu_restore_qos(pd);
}
- for (i = pd->num_clks - 1; i >= 0; i--)
- clk_disable(pd->clks[i]);
+ clk_bulk_disable(pd->num_clks, pd->clks);
}
- mutex_unlock(&pd->pmu->mutex);
+ mutex_unlock(&pmu->mutex);
return 0;
}
@@ -364,8 +368,6 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
const struct rockchip_domain_info *pd_info;
struct rockchip_pm_domain *pd;
struct device_node *qos_node;
- struct clk *clk;
- int clk_cnt;
int i, j;
u32 id;
int error;
@@ -391,41 +393,41 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
return -EINVAL;
}
- clk_cnt = of_count_phandle_with_args(node, "clocks", "#clock-cells");
- pd = devm_kzalloc(pmu->dev,
- sizeof(*pd) + clk_cnt * sizeof(pd->clks[0]),
- GFP_KERNEL);
+ pd = devm_kzalloc(pmu->dev, sizeof(*pd), GFP_KERNEL);
if (!pd)
return -ENOMEM;
pd->info = pd_info;
pd->pmu = pmu;
- for (i = 0; i < clk_cnt; i++) {
- clk = of_clk_get(node, i);
- if (IS_ERR(clk)) {
- error = PTR_ERR(clk);
+ pd->num_clks = of_count_phandle_with_args(node, "clocks",
+ "#clock-cells");
+ if (pd->num_clks > 0) {
+ pd->clks = devm_kcalloc(pmu->dev, pd->num_clks,
+ sizeof(*pd->clks), GFP_KERNEL);
+ if (!pd->clks)
+ return -ENOMEM;
+ } else {
+ dev_dbg(pmu->dev, "%s: doesn't have clocks: %d\n",
+ node->name, pd->num_clks);
+ pd->num_clks = 0;
+ }
+
+ for (i = 0; i < pd->num_clks; i++) {
+ pd->clks[i].clk = of_clk_get(node, i);
+ if (IS_ERR(pd->clks[i].clk)) {
+ error = PTR_ERR(pd->clks[i].clk);
dev_err(pmu->dev,
"%s: failed to get clk at index %d: %d\n",
node->name, i, error);
- goto err_out;
- }
-
- error = clk_prepare(clk);
- if (error) {
- dev_err(pmu->dev,
- "%s: failed to prepare clk %pC (index %d): %d\n",
- node->name, clk, i, error);
- clk_put(clk);
- goto err_out;
+ return error;
}
-
- pd->clks[pd->num_clks++] = clk;
-
- dev_dbg(pmu->dev, "added clock '%pC' to domain '%s'\n",
- clk, node->name);
}
+ error = clk_bulk_prepare(pd->num_clks, pd->clks);
+ if (error)
+ goto err_put_clocks;
+
pd->num_qos = of_count_phandle_with_args(node, "pm_qos",
NULL);
@@ -435,7 +437,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
GFP_KERNEL);
if (!pd->qos_regmap) {
error = -ENOMEM;
- goto err_out;
+ goto err_unprepare_clocks;
}
for (j = 0; j < MAX_QOS_REGS_NUM; j++) {
@@ -445,7 +447,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
GFP_KERNEL);
if (!pd->qos_save_regs[j]) {
error = -ENOMEM;
- goto err_out;
+ goto err_unprepare_clocks;
}
}
@@ -453,13 +455,13 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
qos_node = of_parse_phandle(node, "pm_qos", j);
if (!qos_node) {
error = -ENODEV;
- goto err_out;
+ goto err_unprepare_clocks;
}
pd->qos_regmap[j] = syscon_node_to_regmap(qos_node);
if (IS_ERR(pd->qos_regmap[j])) {
error = -ENODEV;
of_node_put(qos_node);
- goto err_out;
+ goto err_unprepare_clocks;
}
of_node_put(qos_node);
}
@@ -470,7 +472,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
dev_err(pmu->dev,
"failed to power on domain '%s': %d\n",
node->name, error);
- goto err_out;
+ goto err_unprepare_clocks;
}
pd->genpd.name = node->name;
@@ -486,17 +488,16 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
pmu->genpd_data.domains[id] = &pd->genpd;
return 0;
-err_out:
- while (--i >= 0) {
- clk_unprepare(pd->clks[i]);
- clk_put(pd->clks[i]);
- }
+err_unprepare_clocks:
+ clk_bulk_unprepare(pd->num_clks, pd->clks);
+err_put_clocks:
+ clk_bulk_put(pd->num_clks, pd->clks);
return error;
}
static void rockchip_pm_remove_one_domain(struct rockchip_pm_domain *pd)
{
- int i, ret;
+ int ret;
/*
* We're in the error cleanup already, so we only complain,
@@ -507,10 +508,8 @@ static void rockchip_pm_remove_one_domain(struct rockchip_pm_domain *pd)
dev_err(pd->pmu->dev, "failed to remove domain '%s' : %d - state may be inconsistent\n",
pd->genpd.name, ret);
- for (i = 0; i < pd->num_clks; i++) {
- clk_unprepare(pd->clks[i]);
- clk_put(pd->clks[i]);
- }
+ clk_bulk_unprepare(pd->num_clks, pd->clks);
+ clk_bulk_put(pd->num_clks, pd->clks);
/* protect the zeroing of pm->num_clks */
mutex_lock(&pd->pmu->mutex);
diff --git a/drivers/soc/samsung/exynos-pmu.c b/drivers/soc/samsung/exynos-pmu.c
index f56adbd9fb8b..d34ca201b8b7 100644
--- a/drivers/soc/samsung/exynos-pmu.c
+++ b/drivers/soc/samsung/exynos-pmu.c
@@ -85,10 +85,14 @@ static const struct of_device_id exynos_pmu_of_device_ids[] = {
.compatible = "samsung,exynos5250-pmu",
.data = exynos_pmu_data_arm_ptr(exynos5250_pmu_data),
}, {
+ .compatible = "samsung,exynos5410-pmu",
+ }, {
.compatible = "samsung,exynos5420-pmu",
.data = exynos_pmu_data_arm_ptr(exynos5420_pmu_data),
}, {
.compatible = "samsung,exynos5433-pmu",
+ }, {
+ .compatible = "samsung,exynos7-pmu",
},
{ /*sentinel*/ },
};
@@ -126,6 +130,9 @@ static int exynos_pmu_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, pmu_context);
+ if (devm_of_platform_populate(dev))
+ dev_err(dev, "Error populating children, reboot and poweroff might not work properly\n");
+
dev_dbg(dev, "Exynos PMU Driver probe done\n");
return 0;
}
diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
index 89ebe22a3e27..fe4481676da6 100644
--- a/drivers/soc/tegra/Kconfig
+++ b/drivers/soc/tegra/Kconfig
@@ -104,6 +104,16 @@ config ARCH_TEGRA_186_SOC
multi-format support, ISP for image capture processing and BPMP for
power management.
+config ARCH_TEGRA_194_SOC
+ bool "NVIDIA Tegra194 SoC"
+ select MAILBOX
+ select TEGRA_BPMP
+ select TEGRA_HSP_MBOX
+ select TEGRA_IVC
+ select SOC_TEGRA_PMC
+ help
+ Enable support for the NVIDIA Tegra194 SoC.
+
endif
endif
diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index ce62a47a6647..d9fcdb592b39 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -127,8 +127,7 @@ struct tegra_powergate {
unsigned int id;
struct clk **clks;
unsigned int num_clks;
- struct reset_control **resets;
- unsigned int num_resets;
+ struct reset_control *reset;
};
struct tegra_io_pad_soc {
@@ -153,6 +152,7 @@ struct tegra_pmc_soc {
bool has_tsense_reset;
bool has_gpu_clamps;
+ bool needs_mbist_war;
const struct tegra_io_pad_soc *io_pads;
unsigned int num_io_pads;
@@ -368,31 +368,8 @@ out:
return err;
}
-static int tegra_powergate_reset_assert(struct tegra_powergate *pg)
+int __weak tegra210_clk_handle_mbist_war(unsigned int id)
{
- unsigned int i;
- int err;
-
- for (i = 0; i < pg->num_resets; i++) {
- err = reset_control_assert(pg->resets[i]);
- if (err)
- return err;
- }
-
- return 0;
-}
-
-static int tegra_powergate_reset_deassert(struct tegra_powergate *pg)
-{
- unsigned int i;
- int err;
-
- for (i = 0; i < pg->num_resets; i++) {
- err = reset_control_deassert(pg->resets[i]);
- if (err)
- return err;
- }
-
return 0;
}
@@ -401,7 +378,7 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,
{
int err;
- err = tegra_powergate_reset_assert(pg);
+ err = reset_control_assert(pg->reset);
if (err)
return err;
@@ -425,12 +402,17 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,
usleep_range(10, 20);
- err = tegra_powergate_reset_deassert(pg);
+ err = reset_control_deassert(pg->reset);
if (err)
goto powergate_off;
usleep_range(10, 20);
+ if (pg->pmc->soc->needs_mbist_war)
+ err = tegra210_clk_handle_mbist_war(pg->id);
+ if (err)
+ goto disable_clks;
+
if (disable_clocks)
tegra_powergate_disable_clocks(pg);
@@ -456,7 +438,7 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg)
usleep_range(10, 20);
- err = tegra_powergate_reset_assert(pg);
+ err = reset_control_assert(pg->reset);
if (err)
goto disable_clks;
@@ -475,7 +457,7 @@ static int tegra_powergate_power_down(struct tegra_powergate *pg)
assert_resets:
tegra_powergate_enable_clocks(pg);
usleep_range(10, 20);
- tegra_powergate_reset_deassert(pg);
+ reset_control_deassert(pg->reset);
usleep_range(10, 20);
disable_clks:
@@ -586,8 +568,8 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
pg.id = id;
pg.clks = &clk;
pg.num_clks = 1;
- pg.resets = &rst;
- pg.num_resets = 1;
+ pg.reset = rst;
+ pg.pmc = pmc;
err = tegra_powergate_power_up(&pg, false);
if (err)
@@ -775,45 +757,22 @@ err:
static int tegra_powergate_of_get_resets(struct tegra_powergate *pg,
struct device_node *np, bool off)
{
- struct reset_control *rst;
- unsigned int i, count;
int err;
- count = of_count_phandle_with_args(np, "resets", "#reset-cells");
- if (count == 0)
- return -ENODEV;
-
- pg->resets = kcalloc(count, sizeof(rst), GFP_KERNEL);
- if (!pg->resets)
- return -ENOMEM;
-
- for (i = 0; i < count; i++) {
- pg->resets[i] = of_reset_control_get_by_index(np, i);
- if (IS_ERR(pg->resets[i])) {
- err = PTR_ERR(pg->resets[i]);
- goto error;
- }
-
- if (off)
- err = reset_control_assert(pg->resets[i]);
- else
- err = reset_control_deassert(pg->resets[i]);
-
- if (err) {
- reset_control_put(pg->resets[i]);
- goto error;
- }
+ pg->reset = of_reset_control_array_get_exclusive(np);
+ if (IS_ERR(pg->reset)) {
+ err = PTR_ERR(pg->reset);
+ pr_err("failed to get device resets: %d\n", err);
+ return err;
}
- pg->num_resets = count;
-
- return 0;
-
-error:
- while (i--)
- reset_control_put(pg->resets[i]);
+ if (off)
+ err = reset_control_assert(pg->reset);
+ else
+ err = reset_control_deassert(pg->reset);
- kfree(pg->resets);
+ if (err)
+ reset_control_put(pg->reset);
return err;
}
@@ -905,10 +864,7 @@ remove_genpd:
pm_genpd_remove(&pg->genpd);
remove_resets:
- while (pg->num_resets--)
- reset_control_put(pg->resets[pg->num_resets]);
-
- kfree(pg->resets);
+ reset_control_put(pg->reset);
remove_clks:
while (pg->num_clks--)
@@ -1815,6 +1771,7 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = {
.cpu_powergates = tegra210_cpu_powergates,
.has_tsense_reset = true,
.has_gpu_clamps = true,
+ .needs_mbist_war = true,
.num_io_pads = ARRAY_SIZE(tegra210_io_pads),
.io_pads = tegra210_io_pads,
.regs = &tegra20_pmc_regs,
@@ -1920,6 +1877,7 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = {
};
static const struct of_device_id tegra_pmc_match[] = {
+ { .compatible = "nvidia,tegra194-pmc", .data = &tegra186_pmc_soc },
{ .compatible = "nvidia,tegra186-pmc", .data = &tegra186_pmc_soc },
{ .compatible = "nvidia,tegra210-pmc", .data = &tegra210_pmc_soc },
{ .compatible = "nvidia,tegra132-pmc", .data = &tegra124_pmc_soc },
diff --git a/drivers/tee/optee/core.c b/drivers/tee/optee/core.c
index e9843c53fe31..e5fd5ed217da 100644
--- a/drivers/tee/optee/core.c
+++ b/drivers/tee/optee/core.c
@@ -356,6 +356,27 @@ static bool optee_msg_api_uid_is_optee_api(optee_invoke_fn *invoke_fn)
return false;
}
+static void optee_msg_get_os_revision(optee_invoke_fn *invoke_fn)
+{
+ union {
+ struct arm_smccc_res smccc;
+ struct optee_smc_call_get_os_revision_result result;
+ } res = {
+ .result = {
+ .build_id = 0
+ }
+ };
+
+ invoke_fn(OPTEE_SMC_CALL_GET_OS_REVISION, 0, 0, 0, 0, 0, 0, 0,
+ &res.smccc);
+
+ if (res.result.build_id)
+ pr_info("revision %lu.%lu (%08lx)", res.result.major,
+ res.result.minor, res.result.build_id);
+ else
+ pr_info("revision %lu.%lu", res.result.major, res.result.minor);
+}
+
static bool optee_msg_api_revision_is_compatible(optee_invoke_fn *invoke_fn)
{
union {
@@ -547,6 +568,8 @@ static struct optee *optee_probe(struct device_node *np)
return ERR_PTR(-EINVAL);
}
+ optee_msg_get_os_revision(invoke_fn);
+
if (!optee_msg_api_revision_is_compatible(invoke_fn)) {
pr_warn("api revision mismatch\n");
return ERR_PTR(-EINVAL);
diff --git a/drivers/tee/optee/optee_smc.h b/drivers/tee/optee/optee_smc.h
index 7cd327243ada..bbf0cf028c16 100644
--- a/drivers/tee/optee/optee_smc.h
+++ b/drivers/tee/optee/optee_smc.h
@@ -112,12 +112,20 @@ struct optee_smc_calls_revision_result {
* Trusted OS, not of the API.
*
* Returns revision in a0-1 in the same way as OPTEE_SMC_CALLS_REVISION
- * described above.
+ * described above. May optionally return a 32-bit build identifier in a2,
+ * with zero meaning unspecified.
*/
#define OPTEE_SMC_FUNCID_GET_OS_REVISION OPTEE_MSG_FUNCID_GET_OS_REVISION
#define OPTEE_SMC_CALL_GET_OS_REVISION \
OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_GET_OS_REVISION)
+struct optee_smc_call_get_os_revision_result {
+ unsigned long major;
+ unsigned long minor;
+ unsigned long build_id;
+ unsigned long reserved1;
+};
+
/*
* Call with struct optee_msg_arg as argument
*
diff --git a/drivers/tee/tee_core.c b/drivers/tee/tee_core.c
index 6c4b200a4560..0124a91c8d71 100644
--- a/drivers/tee/tee_core.c
+++ b/drivers/tee/tee_core.c
@@ -693,7 +693,7 @@ struct tee_device *tee_device_alloc(const struct tee_desc *teedesc,
{
struct tee_device *teedev;
void *ret;
- int rc;
+ int rc, max_id;
int offs = 0;
if (!teedesc || !teedesc->name || !teedesc->ops ||
@@ -707,16 +707,20 @@ struct tee_device *tee_device_alloc(const struct tee_desc *teedesc,
goto err;
}
- if (teedesc->flags & TEE_DESC_PRIVILEGED)
+ max_id = TEE_NUM_DEVICES / 2;
+
+ if (teedesc->flags & TEE_DESC_PRIVILEGED) {
offs = TEE_NUM_DEVICES / 2;
+ max_id = TEE_NUM_DEVICES;
+ }
spin_lock(&driver_lock);
- teedev->id = find_next_zero_bit(dev_mask, TEE_NUM_DEVICES, offs);
- if (teedev->id < TEE_NUM_DEVICES)
+ teedev->id = find_next_zero_bit(dev_mask, max_id, offs);
+ if (teedev->id < max_id)
set_bit(teedev->id, dev_mask);
spin_unlock(&driver_lock);
- if (teedev->id >= TEE_NUM_DEVICES) {
+ if (teedev->id >= max_id) {
ret = ERR_PTR(-ENOMEM);
goto err;
}