162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Versatile Express Serial Power Controller (SPC) support
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2013 ARM Ltd.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Authors: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com>
862306a36Sopenharmony_ci *          Achin Gupta           <achin.gupta@arm.com>
962306a36Sopenharmony_ci *          Lorenzo Pieralisi     <lorenzo.pieralisi@arm.com>
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/clk-provider.h>
1362306a36Sopenharmony_ci#include <linux/clkdev.h>
1462306a36Sopenharmony_ci#include <linux/cpu.h>
1562306a36Sopenharmony_ci#include <linux/delay.h>
1662306a36Sopenharmony_ci#include <linux/err.h>
1762306a36Sopenharmony_ci#include <linux/interrupt.h>
1862306a36Sopenharmony_ci#include <linux/io.h>
1962306a36Sopenharmony_ci#include <linux/platform_device.h>
2062306a36Sopenharmony_ci#include <linux/pm_opp.h>
2162306a36Sopenharmony_ci#include <linux/slab.h>
2262306a36Sopenharmony_ci#include <linux/semaphore.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <asm/cacheflush.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include "spc.h"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define SPCLOG "vexpress-spc: "
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define PERF_LVL_A15		0x00
3162306a36Sopenharmony_ci#define PERF_REQ_A15		0x04
3262306a36Sopenharmony_ci#define PERF_LVL_A7		0x08
3362306a36Sopenharmony_ci#define PERF_REQ_A7		0x0c
3462306a36Sopenharmony_ci#define COMMS			0x10
3562306a36Sopenharmony_ci#define COMMS_REQ		0x14
3662306a36Sopenharmony_ci#define PWC_STATUS		0x18
3762306a36Sopenharmony_ci#define PWC_FLAG		0x1c
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* SPC wake-up IRQs status and mask */
4062306a36Sopenharmony_ci#define WAKE_INT_MASK		0x24
4162306a36Sopenharmony_ci#define WAKE_INT_RAW		0x28
4262306a36Sopenharmony_ci#define WAKE_INT_STAT		0x2c
4362306a36Sopenharmony_ci/* SPC power down registers */
4462306a36Sopenharmony_ci#define A15_PWRDN_EN		0x30
4562306a36Sopenharmony_ci#define A7_PWRDN_EN		0x34
4662306a36Sopenharmony_ci/* SPC per-CPU mailboxes */
4762306a36Sopenharmony_ci#define A15_BX_ADDR0		0x68
4862306a36Sopenharmony_ci#define A7_BX_ADDR0		0x78
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci/* SPC CPU/cluster reset statue */
5162306a36Sopenharmony_ci#define STANDBYWFI_STAT		0x3c
5262306a36Sopenharmony_ci#define STANDBYWFI_STAT_A15_CPU_MASK(cpu)	(1 << (cpu))
5362306a36Sopenharmony_ci#define STANDBYWFI_STAT_A7_CPU_MASK(cpu)	(1 << (3 + (cpu)))
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci/* SPC system config interface registers */
5662306a36Sopenharmony_ci#define SYSCFG_WDATA		0x70
5762306a36Sopenharmony_ci#define SYSCFG_RDATA		0x74
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/* A15/A7 OPP virtual register base */
6062306a36Sopenharmony_ci#define A15_PERFVAL_BASE	0xC10
6162306a36Sopenharmony_ci#define A7_PERFVAL_BASE		0xC30
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci/* Config interface control bits */
6462306a36Sopenharmony_ci#define SYSCFG_START		BIT(31)
6562306a36Sopenharmony_ci#define SYSCFG_SCC		(6 << 20)
6662306a36Sopenharmony_ci#define SYSCFG_STAT		(14 << 20)
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/* wake-up interrupt masks */
6962306a36Sopenharmony_ci#define GBL_WAKEUP_INT_MSK	(0x3 << 10)
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci/* TC2 static dual-cluster configuration */
7262306a36Sopenharmony_ci#define MAX_CLUSTERS		2
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/*
7562306a36Sopenharmony_ci * Even though the SPC takes max 3-5 ms to complete any OPP/COMMS
7662306a36Sopenharmony_ci * operation, the operation could start just before jiffie is about
7762306a36Sopenharmony_ci * to be incremented. So setting timeout value of 20ms = 2jiffies@100Hz
7862306a36Sopenharmony_ci */
7962306a36Sopenharmony_ci#define TIMEOUT_US	20000
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci#define MAX_OPPS	8
8262306a36Sopenharmony_ci#define CA15_DVFS	0
8362306a36Sopenharmony_ci#define CA7_DVFS	1
8462306a36Sopenharmony_ci#define SPC_SYS_CFG	2
8562306a36Sopenharmony_ci#define STAT_COMPLETE(type)	((1 << 0) << (type << 2))
8662306a36Sopenharmony_ci#define STAT_ERR(type)		((1 << 1) << (type << 2))
8762306a36Sopenharmony_ci#define RESPONSE_MASK(type)	(STAT_COMPLETE(type) | STAT_ERR(type))
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistruct ve_spc_opp {
9062306a36Sopenharmony_ci	unsigned long freq;
9162306a36Sopenharmony_ci	unsigned long u_volt;
9262306a36Sopenharmony_ci};
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistruct ve_spc_drvdata {
9562306a36Sopenharmony_ci	void __iomem *baseaddr;
9662306a36Sopenharmony_ci	/*
9762306a36Sopenharmony_ci	 * A15s cluster identifier
9862306a36Sopenharmony_ci	 * It corresponds to A15 processors MPIDR[15:8] bitfield
9962306a36Sopenharmony_ci	 */
10062306a36Sopenharmony_ci	u32 a15_clusid;
10162306a36Sopenharmony_ci	uint32_t cur_rsp_mask;
10262306a36Sopenharmony_ci	uint32_t cur_rsp_stat;
10362306a36Sopenharmony_ci	struct semaphore sem;
10462306a36Sopenharmony_ci	struct completion done;
10562306a36Sopenharmony_ci	struct ve_spc_opp *opps[MAX_CLUSTERS];
10662306a36Sopenharmony_ci	int num_opps[MAX_CLUSTERS];
10762306a36Sopenharmony_ci};
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic struct ve_spc_drvdata *info;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic inline bool cluster_is_a15(u32 cluster)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	return cluster == info->a15_clusid;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci/**
11762306a36Sopenharmony_ci * ve_spc_global_wakeup_irq() - sets/clears global wakeup IRQs
11862306a36Sopenharmony_ci *
11962306a36Sopenharmony_ci * @set: if true, global wake-up IRQs are set, if false they are cleared
12062306a36Sopenharmony_ci *
12162306a36Sopenharmony_ci * Function to set/clear global wakeup IRQs. Not protected by locking since
12262306a36Sopenharmony_ci * it might be used in code paths where normal cacheable locks are not
12362306a36Sopenharmony_ci * working. Locking must be provided by the caller to ensure atomicity.
12462306a36Sopenharmony_ci */
12562306a36Sopenharmony_civoid ve_spc_global_wakeup_irq(bool set)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	u32 reg;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	if (set)
13262306a36Sopenharmony_ci		reg |= GBL_WAKEUP_INT_MSK;
13362306a36Sopenharmony_ci	else
13462306a36Sopenharmony_ci		reg &= ~GBL_WAKEUP_INT_MSK;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	writel_relaxed(reg, info->baseaddr + WAKE_INT_MASK);
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci/**
14062306a36Sopenharmony_ci * ve_spc_cpu_wakeup_irq() - sets/clears per-CPU wake-up IRQs
14162306a36Sopenharmony_ci *
14262306a36Sopenharmony_ci * @cluster: mpidr[15:8] bitfield describing cluster affinity level
14362306a36Sopenharmony_ci * @cpu: mpidr[7:0] bitfield describing cpu affinity level
14462306a36Sopenharmony_ci * @set: if true, wake-up IRQs are set, if false they are cleared
14562306a36Sopenharmony_ci *
14662306a36Sopenharmony_ci * Function to set/clear per-CPU wake-up IRQs. Not protected by locking since
14762306a36Sopenharmony_ci * it might be used in code paths where normal cacheable locks are not
14862306a36Sopenharmony_ci * working. Locking must be provided by the caller to ensure atomicity.
14962306a36Sopenharmony_ci */
15062306a36Sopenharmony_civoid ve_spc_cpu_wakeup_irq(u32 cluster, u32 cpu, bool set)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	u32 mask, reg;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (cluster >= MAX_CLUSTERS)
15562306a36Sopenharmony_ci		return;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	mask = BIT(cpu);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (!cluster_is_a15(cluster))
16062306a36Sopenharmony_ci		mask <<= 4;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	reg = readl_relaxed(info->baseaddr + WAKE_INT_MASK);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	if (set)
16562306a36Sopenharmony_ci		reg |= mask;
16662306a36Sopenharmony_ci	else
16762306a36Sopenharmony_ci		reg &= ~mask;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	writel_relaxed(reg, info->baseaddr + WAKE_INT_MASK);
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci/**
17362306a36Sopenharmony_ci * ve_spc_set_resume_addr() - set the jump address used for warm boot
17462306a36Sopenharmony_ci *
17562306a36Sopenharmony_ci * @cluster: mpidr[15:8] bitfield describing cluster affinity level
17662306a36Sopenharmony_ci * @cpu: mpidr[7:0] bitfield describing cpu affinity level
17762306a36Sopenharmony_ci * @addr: physical resume address
17862306a36Sopenharmony_ci */
17962306a36Sopenharmony_civoid ve_spc_set_resume_addr(u32 cluster, u32 cpu, u32 addr)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	void __iomem *baseaddr;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	if (cluster >= MAX_CLUSTERS)
18462306a36Sopenharmony_ci		return;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (cluster_is_a15(cluster))
18762306a36Sopenharmony_ci		baseaddr = info->baseaddr + A15_BX_ADDR0 + (cpu << 2);
18862306a36Sopenharmony_ci	else
18962306a36Sopenharmony_ci		baseaddr = info->baseaddr + A7_BX_ADDR0 + (cpu << 2);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	writel_relaxed(addr, baseaddr);
19262306a36Sopenharmony_ci}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci/**
19562306a36Sopenharmony_ci * ve_spc_powerdown() - enables/disables cluster powerdown
19662306a36Sopenharmony_ci *
19762306a36Sopenharmony_ci * @cluster: mpidr[15:8] bitfield describing cluster affinity level
19862306a36Sopenharmony_ci * @enable: if true enables powerdown, if false disables it
19962306a36Sopenharmony_ci *
20062306a36Sopenharmony_ci * Function to enable/disable cluster powerdown. Not protected by locking
20162306a36Sopenharmony_ci * since it might be used in code paths where normal cacheable locks are not
20262306a36Sopenharmony_ci * working. Locking must be provided by the caller to ensure atomicity.
20362306a36Sopenharmony_ci */
20462306a36Sopenharmony_civoid ve_spc_powerdown(u32 cluster, bool enable)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	u32 pwdrn_reg;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (cluster >= MAX_CLUSTERS)
20962306a36Sopenharmony_ci		return;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	pwdrn_reg = cluster_is_a15(cluster) ? A15_PWRDN_EN : A7_PWRDN_EN;
21262306a36Sopenharmony_ci	writel_relaxed(enable, info->baseaddr + pwdrn_reg);
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic u32 standbywfi_cpu_mask(u32 cpu, u32 cluster)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	return cluster_is_a15(cluster) ?
21862306a36Sopenharmony_ci		  STANDBYWFI_STAT_A15_CPU_MASK(cpu)
21962306a36Sopenharmony_ci		: STANDBYWFI_STAT_A7_CPU_MASK(cpu);
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci/**
22362306a36Sopenharmony_ci * ve_spc_cpu_in_wfi() - Checks if the specified CPU is in WFI or not
22462306a36Sopenharmony_ci *
22562306a36Sopenharmony_ci * @cpu: mpidr[7:0] bitfield describing CPU affinity level within cluster
22662306a36Sopenharmony_ci * @cluster: mpidr[15:8] bitfield describing cluster affinity level
22762306a36Sopenharmony_ci *
22862306a36Sopenharmony_ci * @return: non-zero if and only if the specified CPU is in WFI
22962306a36Sopenharmony_ci *
23062306a36Sopenharmony_ci * Take care when interpreting the result of this function: a CPU might
23162306a36Sopenharmony_ci * be in WFI temporarily due to idle, and is not necessarily safely
23262306a36Sopenharmony_ci * parked.
23362306a36Sopenharmony_ci */
23462306a36Sopenharmony_ciint ve_spc_cpu_in_wfi(u32 cpu, u32 cluster)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	int ret;
23762306a36Sopenharmony_ci	u32 mask = standbywfi_cpu_mask(cpu, cluster);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	if (cluster >= MAX_CLUSTERS)
24062306a36Sopenharmony_ci		return 1;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	ret = readl_relaxed(info->baseaddr + STANDBYWFI_STAT);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	pr_debug("%s: PCFGREG[0x%X] = 0x%08X, mask = 0x%X\n",
24562306a36Sopenharmony_ci		 __func__, STANDBYWFI_STAT, ret, mask);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return ret & mask;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic int ve_spc_get_performance(int cluster, u32 *freq)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct ve_spc_opp *opps = info->opps[cluster];
25362306a36Sopenharmony_ci	u32 perf_cfg_reg = 0;
25462306a36Sopenharmony_ci	u32 perf;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	perf_cfg_reg = cluster_is_a15(cluster) ? PERF_LVL_A15 : PERF_LVL_A7;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	perf = readl_relaxed(info->baseaddr + perf_cfg_reg);
25962306a36Sopenharmony_ci	if (perf >= info->num_opps[cluster])
26062306a36Sopenharmony_ci		return -EINVAL;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	opps += perf;
26362306a36Sopenharmony_ci	*freq = opps->freq;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	return 0;
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci/* find closest match to given frequency in OPP table */
26962306a36Sopenharmony_cistatic int ve_spc_round_performance(int cluster, u32 freq)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	int idx, max_opp = info->num_opps[cluster];
27262306a36Sopenharmony_ci	struct ve_spc_opp *opps = info->opps[cluster];
27362306a36Sopenharmony_ci	u32 fmin = 0, fmax = ~0, ftmp;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	freq /= 1000; /* OPP entries in kHz */
27662306a36Sopenharmony_ci	for (idx = 0; idx < max_opp; idx++, opps++) {
27762306a36Sopenharmony_ci		ftmp = opps->freq;
27862306a36Sopenharmony_ci		if (ftmp >= freq) {
27962306a36Sopenharmony_ci			if (ftmp <= fmax)
28062306a36Sopenharmony_ci				fmax = ftmp;
28162306a36Sopenharmony_ci		} else {
28262306a36Sopenharmony_ci			if (ftmp >= fmin)
28362306a36Sopenharmony_ci				fmin = ftmp;
28462306a36Sopenharmony_ci		}
28562306a36Sopenharmony_ci	}
28662306a36Sopenharmony_ci	if (fmax != ~0)
28762306a36Sopenharmony_ci		return fmax * 1000;
28862306a36Sopenharmony_ci	else
28962306a36Sopenharmony_ci		return fmin * 1000;
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cistatic int ve_spc_find_performance_index(int cluster, u32 freq)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	int idx, max_opp = info->num_opps[cluster];
29562306a36Sopenharmony_ci	struct ve_spc_opp *opps = info->opps[cluster];
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	for (idx = 0; idx < max_opp; idx++, opps++)
29862306a36Sopenharmony_ci		if (opps->freq == freq)
29962306a36Sopenharmony_ci			break;
30062306a36Sopenharmony_ci	return (idx == max_opp) ? -EINVAL : idx;
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic int ve_spc_waitforcompletion(int req_type)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	int ret = wait_for_completion_interruptible_timeout(
30662306a36Sopenharmony_ci			&info->done, usecs_to_jiffies(TIMEOUT_US));
30762306a36Sopenharmony_ci	if (ret == 0)
30862306a36Sopenharmony_ci		ret = -ETIMEDOUT;
30962306a36Sopenharmony_ci	else if (ret > 0)
31062306a36Sopenharmony_ci		ret = info->cur_rsp_stat & STAT_COMPLETE(req_type) ? 0 : -EIO;
31162306a36Sopenharmony_ci	return ret;
31262306a36Sopenharmony_ci}
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cistatic int ve_spc_set_performance(int cluster, u32 freq)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	u32 perf_cfg_reg;
31762306a36Sopenharmony_ci	int ret, perf, req_type;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	if (cluster_is_a15(cluster)) {
32062306a36Sopenharmony_ci		req_type = CA15_DVFS;
32162306a36Sopenharmony_ci		perf_cfg_reg = PERF_LVL_A15;
32262306a36Sopenharmony_ci	} else {
32362306a36Sopenharmony_ci		req_type = CA7_DVFS;
32462306a36Sopenharmony_ci		perf_cfg_reg = PERF_LVL_A7;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	perf = ve_spc_find_performance_index(cluster, freq);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	if (perf < 0)
33062306a36Sopenharmony_ci		return perf;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	if (down_timeout(&info->sem, usecs_to_jiffies(TIMEOUT_US)))
33362306a36Sopenharmony_ci		return -ETIME;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	init_completion(&info->done);
33662306a36Sopenharmony_ci	info->cur_rsp_mask = RESPONSE_MASK(req_type);
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	writel(perf, info->baseaddr + perf_cfg_reg);
33962306a36Sopenharmony_ci	ret = ve_spc_waitforcompletion(req_type);
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	info->cur_rsp_mask = 0;
34262306a36Sopenharmony_ci	up(&info->sem);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	return ret;
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic int ve_spc_read_sys_cfg(int func, int offset, uint32_t *data)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	int ret;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	if (down_timeout(&info->sem, usecs_to_jiffies(TIMEOUT_US)))
35262306a36Sopenharmony_ci		return -ETIME;
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	init_completion(&info->done);
35562306a36Sopenharmony_ci	info->cur_rsp_mask = RESPONSE_MASK(SPC_SYS_CFG);
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	/* Set the control value */
35862306a36Sopenharmony_ci	writel(SYSCFG_START | func | offset >> 2, info->baseaddr + COMMS);
35962306a36Sopenharmony_ci	ret = ve_spc_waitforcompletion(SPC_SYS_CFG);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	if (ret == 0)
36262306a36Sopenharmony_ci		*data = readl(info->baseaddr + SYSCFG_RDATA);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	info->cur_rsp_mask = 0;
36562306a36Sopenharmony_ci	up(&info->sem);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	return ret;
36862306a36Sopenharmony_ci}
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_cistatic irqreturn_t ve_spc_irq_handler(int irq, void *data)
37162306a36Sopenharmony_ci{
37262306a36Sopenharmony_ci	struct ve_spc_drvdata *drv_data = data;
37362306a36Sopenharmony_ci	uint32_t status = readl_relaxed(drv_data->baseaddr + PWC_STATUS);
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	if (info->cur_rsp_mask & status) {
37662306a36Sopenharmony_ci		info->cur_rsp_stat = status;
37762306a36Sopenharmony_ci		complete(&drv_data->done);
37862306a36Sopenharmony_ci	}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	return IRQ_HANDLED;
38162306a36Sopenharmony_ci}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci/*
38462306a36Sopenharmony_ci *  +--------------------------+
38562306a36Sopenharmony_ci *  | 31      20 | 19        0 |
38662306a36Sopenharmony_ci *  +--------------------------+
38762306a36Sopenharmony_ci *  |   m_volt   |  freq(kHz)  |
38862306a36Sopenharmony_ci *  +--------------------------+
38962306a36Sopenharmony_ci */
39062306a36Sopenharmony_ci#define MULT_FACTOR	20
39162306a36Sopenharmony_ci#define VOLT_SHIFT	20
39262306a36Sopenharmony_ci#define FREQ_MASK	(0xFFFFF)
39362306a36Sopenharmony_cistatic int ve_spc_populate_opps(uint32_t cluster)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	uint32_t data = 0, off, ret, idx;
39662306a36Sopenharmony_ci	struct ve_spc_opp *opps;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	opps = kcalloc(MAX_OPPS, sizeof(*opps), GFP_KERNEL);
39962306a36Sopenharmony_ci	if (!opps)
40062306a36Sopenharmony_ci		return -ENOMEM;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	info->opps[cluster] = opps;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	off = cluster_is_a15(cluster) ? A15_PERFVAL_BASE : A7_PERFVAL_BASE;
40562306a36Sopenharmony_ci	for (idx = 0; idx < MAX_OPPS; idx++, off += 4, opps++) {
40662306a36Sopenharmony_ci		ret = ve_spc_read_sys_cfg(SYSCFG_SCC, off, &data);
40762306a36Sopenharmony_ci		if (!ret) {
40862306a36Sopenharmony_ci			opps->freq = (data & FREQ_MASK) * MULT_FACTOR;
40962306a36Sopenharmony_ci			opps->u_volt = (data >> VOLT_SHIFT) * 1000;
41062306a36Sopenharmony_ci		} else {
41162306a36Sopenharmony_ci			break;
41262306a36Sopenharmony_ci		}
41362306a36Sopenharmony_ci	}
41462306a36Sopenharmony_ci	info->num_opps[cluster] = idx;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	return ret;
41762306a36Sopenharmony_ci}
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_cistatic int ve_init_opp_table(struct device *cpu_dev)
42062306a36Sopenharmony_ci{
42162306a36Sopenharmony_ci	int cluster;
42262306a36Sopenharmony_ci	int idx, ret = 0, max_opp;
42362306a36Sopenharmony_ci	struct ve_spc_opp *opps;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	cluster = topology_physical_package_id(cpu_dev->id);
42662306a36Sopenharmony_ci	cluster = cluster < 0 ? 0 : cluster;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	max_opp = info->num_opps[cluster];
42962306a36Sopenharmony_ci	opps = info->opps[cluster];
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	for (idx = 0; idx < max_opp; idx++, opps++) {
43262306a36Sopenharmony_ci		ret = dev_pm_opp_add(cpu_dev, opps->freq * 1000, opps->u_volt);
43362306a36Sopenharmony_ci		if (ret) {
43462306a36Sopenharmony_ci			dev_warn(cpu_dev, "failed to add opp %lu %lu\n",
43562306a36Sopenharmony_ci				 opps->freq, opps->u_volt);
43662306a36Sopenharmony_ci			return ret;
43762306a36Sopenharmony_ci		}
43862306a36Sopenharmony_ci	}
43962306a36Sopenharmony_ci	return ret;
44062306a36Sopenharmony_ci}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ciint __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid, int irq)
44362306a36Sopenharmony_ci{
44462306a36Sopenharmony_ci	int ret;
44562306a36Sopenharmony_ci	info = kzalloc(sizeof(*info), GFP_KERNEL);
44662306a36Sopenharmony_ci	if (!info)
44762306a36Sopenharmony_ci		return -ENOMEM;
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	info->baseaddr = baseaddr;
45062306a36Sopenharmony_ci	info->a15_clusid = a15_clusid;
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	if (irq <= 0) {
45362306a36Sopenharmony_ci		pr_err(SPCLOG "Invalid IRQ %d\n", irq);
45462306a36Sopenharmony_ci		kfree(info);
45562306a36Sopenharmony_ci		return -EINVAL;
45662306a36Sopenharmony_ci	}
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	init_completion(&info->done);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	readl_relaxed(info->baseaddr + PWC_STATUS);
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	ret = request_irq(irq, ve_spc_irq_handler, IRQF_TRIGGER_HIGH
46362306a36Sopenharmony_ci				| IRQF_ONESHOT, "vexpress-spc", info);
46462306a36Sopenharmony_ci	if (ret) {
46562306a36Sopenharmony_ci		pr_err(SPCLOG "IRQ %d request failed\n", irq);
46662306a36Sopenharmony_ci		kfree(info);
46762306a36Sopenharmony_ci		return -ENODEV;
46862306a36Sopenharmony_ci	}
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	sema_init(&info->sem, 1);
47162306a36Sopenharmony_ci	/*
47262306a36Sopenharmony_ci	 * Multi-cluster systems may need this data when non-coherent, during
47362306a36Sopenharmony_ci	 * cluster power-up/power-down. Make sure driver info reaches main
47462306a36Sopenharmony_ci	 * memory.
47562306a36Sopenharmony_ci	 */
47662306a36Sopenharmony_ci	sync_cache_w(info);
47762306a36Sopenharmony_ci	sync_cache_w(&info);
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	return 0;
48062306a36Sopenharmony_ci}
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistruct clk_spc {
48362306a36Sopenharmony_ci	struct clk_hw hw;
48462306a36Sopenharmony_ci	int cluster;
48562306a36Sopenharmony_ci};
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci#define to_clk_spc(spc) container_of(spc, struct clk_spc, hw)
48862306a36Sopenharmony_cistatic unsigned long spc_recalc_rate(struct clk_hw *hw,
48962306a36Sopenharmony_ci		unsigned long parent_rate)
49062306a36Sopenharmony_ci{
49162306a36Sopenharmony_ci	struct clk_spc *spc = to_clk_spc(hw);
49262306a36Sopenharmony_ci	u32 freq;
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	if (ve_spc_get_performance(spc->cluster, &freq))
49562306a36Sopenharmony_ci		return -EIO;
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	return freq * 1000;
49862306a36Sopenharmony_ci}
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_cistatic long spc_round_rate(struct clk_hw *hw, unsigned long drate,
50162306a36Sopenharmony_ci		unsigned long *parent_rate)
50262306a36Sopenharmony_ci{
50362306a36Sopenharmony_ci	struct clk_spc *spc = to_clk_spc(hw);
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	return ve_spc_round_performance(spc->cluster, drate);
50662306a36Sopenharmony_ci}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_cistatic int spc_set_rate(struct clk_hw *hw, unsigned long rate,
50962306a36Sopenharmony_ci		unsigned long parent_rate)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	struct clk_spc *spc = to_clk_spc(hw);
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	return ve_spc_set_performance(spc->cluster, rate / 1000);
51462306a36Sopenharmony_ci}
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cistatic struct clk_ops clk_spc_ops = {
51762306a36Sopenharmony_ci	.recalc_rate = spc_recalc_rate,
51862306a36Sopenharmony_ci	.round_rate = spc_round_rate,
51962306a36Sopenharmony_ci	.set_rate = spc_set_rate,
52062306a36Sopenharmony_ci};
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_cistatic struct clk *ve_spc_clk_register(struct device *cpu_dev)
52362306a36Sopenharmony_ci{
52462306a36Sopenharmony_ci	struct clk_init_data init;
52562306a36Sopenharmony_ci	struct clk_spc *spc;
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	spc = kzalloc(sizeof(*spc), GFP_KERNEL);
52862306a36Sopenharmony_ci	if (!spc)
52962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	spc->hw.init = &init;
53262306a36Sopenharmony_ci	spc->cluster = topology_physical_package_id(cpu_dev->id);
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	spc->cluster = spc->cluster < 0 ? 0 : spc->cluster;
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	init.name = dev_name(cpu_dev);
53762306a36Sopenharmony_ci	init.ops = &clk_spc_ops;
53862306a36Sopenharmony_ci	init.flags = CLK_GET_RATE_NOCACHE;
53962306a36Sopenharmony_ci	init.num_parents = 0;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	return devm_clk_register(cpu_dev, &spc->hw);
54262306a36Sopenharmony_ci}
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_cistatic int __init ve_spc_clk_init(void)
54562306a36Sopenharmony_ci{
54662306a36Sopenharmony_ci	int cpu, cluster;
54762306a36Sopenharmony_ci	struct clk *clk;
54862306a36Sopenharmony_ci	bool init_opp_table[MAX_CLUSTERS] = { false };
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	if (!info)
55162306a36Sopenharmony_ci		return 0; /* Continue only if SPC is initialised */
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	if (ve_spc_populate_opps(0) || ve_spc_populate_opps(1)) {
55462306a36Sopenharmony_ci		pr_err("failed to build OPP table\n");
55562306a36Sopenharmony_ci		return -ENODEV;
55662306a36Sopenharmony_ci	}
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	for_each_possible_cpu(cpu) {
55962306a36Sopenharmony_ci		struct device *cpu_dev = get_cpu_device(cpu);
56062306a36Sopenharmony_ci		if (!cpu_dev) {
56162306a36Sopenharmony_ci			pr_warn("failed to get cpu%d device\n", cpu);
56262306a36Sopenharmony_ci			continue;
56362306a36Sopenharmony_ci		}
56462306a36Sopenharmony_ci		clk = ve_spc_clk_register(cpu_dev);
56562306a36Sopenharmony_ci		if (IS_ERR(clk)) {
56662306a36Sopenharmony_ci			pr_warn("failed to register cpu%d clock\n", cpu);
56762306a36Sopenharmony_ci			continue;
56862306a36Sopenharmony_ci		}
56962306a36Sopenharmony_ci		if (clk_register_clkdev(clk, NULL, dev_name(cpu_dev))) {
57062306a36Sopenharmony_ci			pr_warn("failed to register cpu%d clock lookup\n", cpu);
57162306a36Sopenharmony_ci			continue;
57262306a36Sopenharmony_ci		}
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci		cluster = topology_physical_package_id(cpu_dev->id);
57562306a36Sopenharmony_ci		if (cluster < 0 || init_opp_table[cluster])
57662306a36Sopenharmony_ci			continue;
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci		if (ve_init_opp_table(cpu_dev))
57962306a36Sopenharmony_ci			pr_warn("failed to initialise cpu%d opp table\n", cpu);
58062306a36Sopenharmony_ci		else if (dev_pm_opp_set_sharing_cpus(cpu_dev,
58162306a36Sopenharmony_ci			 topology_core_cpumask(cpu_dev->id)))
58262306a36Sopenharmony_ci			pr_warn("failed to mark OPPs shared for cpu%d\n", cpu);
58362306a36Sopenharmony_ci		else
58462306a36Sopenharmony_ci			init_opp_table[cluster] = true;
58562306a36Sopenharmony_ci	}
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	platform_device_register_simple("vexpress-spc-cpufreq", -1, NULL, 0);
58862306a36Sopenharmony_ci	return 0;
58962306a36Sopenharmony_ci}
59062306a36Sopenharmony_cidevice_initcall(ve_spc_clk_init);
591