162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * CPU frequency scaling for Broadcom SoCs with AVS firmware that
362306a36Sopenharmony_ci * supports DVS or DVFS
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2016 Broadcom
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or
862306a36Sopenharmony_ci * modify it under the terms of the GNU General Public License as
962306a36Sopenharmony_ci * published by the Free Software Foundation version 2.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any
1262306a36Sopenharmony_ci * kind, whether express or implied; without even the implied warranty
1362306a36Sopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1462306a36Sopenharmony_ci * GNU General Public License for more details.
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/*
1862306a36Sopenharmony_ci * "AVS" is the name of a firmware developed at Broadcom. It derives
1962306a36Sopenharmony_ci * its name from the technique called "Adaptive Voltage Scaling".
2062306a36Sopenharmony_ci * Adaptive voltage scaling was the original purpose of this firmware.
2162306a36Sopenharmony_ci * The AVS firmware still supports "AVS mode", where all it does is
2262306a36Sopenharmony_ci * adaptive voltage scaling. However, on some newer Broadcom SoCs, the
2362306a36Sopenharmony_ci * AVS Firmware, despite its unchanged name, also supports DFS mode and
2462306a36Sopenharmony_ci * DVFS mode.
2562306a36Sopenharmony_ci *
2662306a36Sopenharmony_ci * In the context of this document and the related driver, "AVS" by
2762306a36Sopenharmony_ci * itself always means the Broadcom firmware and never refers to the
2862306a36Sopenharmony_ci * technique called "Adaptive Voltage Scaling".
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * The Broadcom STB AVS CPUfreq driver provides voltage and frequency
3162306a36Sopenharmony_ci * scaling on Broadcom SoCs using AVS firmware with support for DFS and
3262306a36Sopenharmony_ci * DVFS. The AVS firmware is running on its own co-processor. The
3362306a36Sopenharmony_ci * driver supports both uniprocessor (UP) and symmetric multiprocessor
3462306a36Sopenharmony_ci * (SMP) systems which share clock and voltage across all CPUs.
3562306a36Sopenharmony_ci *
3662306a36Sopenharmony_ci * Actual voltage and frequency scaling is done solely by the AVS
3762306a36Sopenharmony_ci * firmware. This driver does not change frequency or voltage itself.
3862306a36Sopenharmony_ci * It provides a standard CPUfreq interface to the rest of the kernel
3962306a36Sopenharmony_ci * and to userland. It interfaces with the AVS firmware to effect the
4062306a36Sopenharmony_ci * requested changes and to report back the current system status in a
4162306a36Sopenharmony_ci * way that is expected by existing tools.
4262306a36Sopenharmony_ci */
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#include <linux/cpufreq.h>
4562306a36Sopenharmony_ci#include <linux/delay.h>
4662306a36Sopenharmony_ci#include <linux/interrupt.h>
4762306a36Sopenharmony_ci#include <linux/io.h>
4862306a36Sopenharmony_ci#include <linux/module.h>
4962306a36Sopenharmony_ci#include <linux/of_address.h>
5062306a36Sopenharmony_ci#include <linux/platform_device.h>
5162306a36Sopenharmony_ci#include <linux/semaphore.h>
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci/* Max number of arguments AVS calls take */
5462306a36Sopenharmony_ci#define AVS_MAX_CMD_ARGS	4
5562306a36Sopenharmony_ci/*
5662306a36Sopenharmony_ci * This macro is used to generate AVS parameter register offsets. For
5762306a36Sopenharmony_ci * x >= AVS_MAX_CMD_ARGS, it returns 0 to protect against accidental memory
5862306a36Sopenharmony_ci * access outside of the parameter range. (Offset 0 is the first parameter.)
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_ci#define AVS_PARAM_MULT(x)	((x) < AVS_MAX_CMD_ARGS ? (x) : 0)
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/* AVS Mailbox Register offsets */
6362306a36Sopenharmony_ci#define AVS_MBOX_COMMAND	0x00
6462306a36Sopenharmony_ci#define AVS_MBOX_STATUS		0x04
6562306a36Sopenharmony_ci#define AVS_MBOX_VOLTAGE0	0x08
6662306a36Sopenharmony_ci#define AVS_MBOX_TEMP0		0x0c
6762306a36Sopenharmony_ci#define AVS_MBOX_PV0		0x10
6862306a36Sopenharmony_ci#define AVS_MBOX_MV0		0x14
6962306a36Sopenharmony_ci#define AVS_MBOX_PARAM(x)	(0x18 + AVS_PARAM_MULT(x) * sizeof(u32))
7062306a36Sopenharmony_ci#define AVS_MBOX_REVISION	0x28
7162306a36Sopenharmony_ci#define AVS_MBOX_PSTATE		0x2c
7262306a36Sopenharmony_ci#define AVS_MBOX_HEARTBEAT	0x30
7362306a36Sopenharmony_ci#define AVS_MBOX_MAGIC		0x34
7462306a36Sopenharmony_ci#define AVS_MBOX_SIGMA_HVT	0x38
7562306a36Sopenharmony_ci#define AVS_MBOX_SIGMA_SVT	0x3c
7662306a36Sopenharmony_ci#define AVS_MBOX_VOLTAGE1	0x40
7762306a36Sopenharmony_ci#define AVS_MBOX_TEMP1		0x44
7862306a36Sopenharmony_ci#define AVS_MBOX_PV1		0x48
7962306a36Sopenharmony_ci#define AVS_MBOX_MV1		0x4c
8062306a36Sopenharmony_ci#define AVS_MBOX_FREQUENCY	0x50
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci/* AVS Commands */
8362306a36Sopenharmony_ci#define AVS_CMD_AVAILABLE	0x00
8462306a36Sopenharmony_ci#define AVS_CMD_DISABLE		0x10
8562306a36Sopenharmony_ci#define AVS_CMD_ENABLE		0x11
8662306a36Sopenharmony_ci#define AVS_CMD_S2_ENTER	0x12
8762306a36Sopenharmony_ci#define AVS_CMD_S2_EXIT		0x13
8862306a36Sopenharmony_ci#define AVS_CMD_BBM_ENTER	0x14
8962306a36Sopenharmony_ci#define AVS_CMD_BBM_EXIT	0x15
9062306a36Sopenharmony_ci#define AVS_CMD_S3_ENTER	0x16
9162306a36Sopenharmony_ci#define AVS_CMD_S3_EXIT		0x17
9262306a36Sopenharmony_ci#define AVS_CMD_BALANCE		0x18
9362306a36Sopenharmony_ci/* PMAP and P-STATE commands */
9462306a36Sopenharmony_ci#define AVS_CMD_GET_PMAP	0x30
9562306a36Sopenharmony_ci#define AVS_CMD_SET_PMAP	0x31
9662306a36Sopenharmony_ci#define AVS_CMD_GET_PSTATE	0x40
9762306a36Sopenharmony_ci#define AVS_CMD_SET_PSTATE	0x41
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci/* Different modes AVS supports (for GET_PMAP/SET_PMAP) */
10062306a36Sopenharmony_ci#define AVS_MODE_AVS		0x0
10162306a36Sopenharmony_ci#define AVS_MODE_DFS		0x1
10262306a36Sopenharmony_ci#define AVS_MODE_DVS		0x2
10362306a36Sopenharmony_ci#define AVS_MODE_DVFS		0x3
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/*
10662306a36Sopenharmony_ci * PMAP parameter p1
10762306a36Sopenharmony_ci * unused:31-24, mdiv_p0:23-16, unused:15-14, pdiv:13-10 , ndiv_int:9-0
10862306a36Sopenharmony_ci */
10962306a36Sopenharmony_ci#define NDIV_INT_SHIFT		0
11062306a36Sopenharmony_ci#define NDIV_INT_MASK		0x3ff
11162306a36Sopenharmony_ci#define PDIV_SHIFT		10
11262306a36Sopenharmony_ci#define PDIV_MASK		0xf
11362306a36Sopenharmony_ci#define MDIV_P0_SHIFT		16
11462306a36Sopenharmony_ci#define MDIV_P0_MASK		0xff
11562306a36Sopenharmony_ci/*
11662306a36Sopenharmony_ci * PMAP parameter p2
11762306a36Sopenharmony_ci * mdiv_p4:31-24, mdiv_p3:23-16, mdiv_p2:15:8, mdiv_p1:7:0
11862306a36Sopenharmony_ci */
11962306a36Sopenharmony_ci#define MDIV_P1_SHIFT		0
12062306a36Sopenharmony_ci#define MDIV_P1_MASK		0xff
12162306a36Sopenharmony_ci#define MDIV_P2_SHIFT		8
12262306a36Sopenharmony_ci#define MDIV_P2_MASK		0xff
12362306a36Sopenharmony_ci#define MDIV_P3_SHIFT		16
12462306a36Sopenharmony_ci#define MDIV_P3_MASK		0xff
12562306a36Sopenharmony_ci#define MDIV_P4_SHIFT		24
12662306a36Sopenharmony_ci#define MDIV_P4_MASK		0xff
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci/* Different P-STATES AVS supports (for GET_PSTATE/SET_PSTATE) */
12962306a36Sopenharmony_ci#define AVS_PSTATE_P0		0x0
13062306a36Sopenharmony_ci#define AVS_PSTATE_P1		0x1
13162306a36Sopenharmony_ci#define AVS_PSTATE_P2		0x2
13262306a36Sopenharmony_ci#define AVS_PSTATE_P3		0x3
13362306a36Sopenharmony_ci#define AVS_PSTATE_P4		0x4
13462306a36Sopenharmony_ci#define AVS_PSTATE_MAX		AVS_PSTATE_P4
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci/* CPU L2 Interrupt Controller Registers */
13762306a36Sopenharmony_ci#define AVS_CPU_L2_SET0		0x04
13862306a36Sopenharmony_ci#define AVS_CPU_L2_INT_MASK	BIT(31)
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci/* AVS Command Status Values */
14162306a36Sopenharmony_ci#define AVS_STATUS_CLEAR	0x00
14262306a36Sopenharmony_ci/* Command/notification accepted */
14362306a36Sopenharmony_ci#define AVS_STATUS_SUCCESS	0xf0
14462306a36Sopenharmony_ci/* Command/notification rejected */
14562306a36Sopenharmony_ci#define AVS_STATUS_FAILURE	0xff
14662306a36Sopenharmony_ci/* Invalid command/notification (unknown) */
14762306a36Sopenharmony_ci#define AVS_STATUS_INVALID	0xf1
14862306a36Sopenharmony_ci/* Non-AVS modes are not supported */
14962306a36Sopenharmony_ci#define AVS_STATUS_NO_SUPP	0xf2
15062306a36Sopenharmony_ci/* Cannot set P-State until P-Map supplied */
15162306a36Sopenharmony_ci#define AVS_STATUS_NO_MAP	0xf3
15262306a36Sopenharmony_ci/* Cannot change P-Map after initial P-Map set */
15362306a36Sopenharmony_ci#define AVS_STATUS_MAP_SET	0xf4
15462306a36Sopenharmony_ci/* Max AVS status; higher numbers are used for debugging */
15562306a36Sopenharmony_ci#define AVS_STATUS_MAX		0xff
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci/* Other AVS related constants */
15862306a36Sopenharmony_ci#define AVS_LOOP_LIMIT		10000
15962306a36Sopenharmony_ci#define AVS_TIMEOUT		300 /* in ms; expected completion is < 10ms */
16062306a36Sopenharmony_ci#define AVS_FIRMWARE_MAGIC	0xa11600d1
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci#define BRCM_AVS_CPUFREQ_PREFIX	"brcmstb-avs"
16362306a36Sopenharmony_ci#define BRCM_AVS_CPUFREQ_NAME	BRCM_AVS_CPUFREQ_PREFIX "-cpufreq"
16462306a36Sopenharmony_ci#define BRCM_AVS_CPU_DATA	"brcm,avs-cpu-data-mem"
16562306a36Sopenharmony_ci#define BRCM_AVS_CPU_INTR	"brcm,avs-cpu-l2-intr"
16662306a36Sopenharmony_ci#define BRCM_AVS_HOST_INTR	"sw_intr"
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistruct pmap {
16962306a36Sopenharmony_ci	unsigned int mode;
17062306a36Sopenharmony_ci	unsigned int p1;
17162306a36Sopenharmony_ci	unsigned int p2;
17262306a36Sopenharmony_ci	unsigned int state;
17362306a36Sopenharmony_ci};
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistruct private_data {
17662306a36Sopenharmony_ci	void __iomem *base;
17762306a36Sopenharmony_ci	void __iomem *avs_intr_base;
17862306a36Sopenharmony_ci	struct device *dev;
17962306a36Sopenharmony_ci	struct completion done;
18062306a36Sopenharmony_ci	struct semaphore sem;
18162306a36Sopenharmony_ci	struct pmap pmap;
18262306a36Sopenharmony_ci	int host_irq;
18362306a36Sopenharmony_ci};
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic void __iomem *__map_region(const char *name)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct device_node *np;
18862306a36Sopenharmony_ci	void __iomem *ptr;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	np = of_find_compatible_node(NULL, NULL, name);
19162306a36Sopenharmony_ci	if (!np)
19262306a36Sopenharmony_ci		return NULL;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	ptr = of_iomap(np, 0);
19562306a36Sopenharmony_ci	of_node_put(np);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return ptr;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic unsigned long wait_for_avs_command(struct private_data *priv,
20162306a36Sopenharmony_ci					  unsigned long timeout)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	unsigned long time_left = 0;
20462306a36Sopenharmony_ci	u32 val;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	/* Event driven, wait for the command interrupt */
20762306a36Sopenharmony_ci	if (priv->host_irq >= 0)
20862306a36Sopenharmony_ci		return wait_for_completion_timeout(&priv->done,
20962306a36Sopenharmony_ci						   msecs_to_jiffies(timeout));
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	/* Polling for command completion */
21262306a36Sopenharmony_ci	do {
21362306a36Sopenharmony_ci		time_left = timeout;
21462306a36Sopenharmony_ci		val = readl(priv->base + AVS_MBOX_STATUS);
21562306a36Sopenharmony_ci		if (val)
21662306a36Sopenharmony_ci			break;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci		usleep_range(1000, 2000);
21962306a36Sopenharmony_ci	} while (--timeout);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	return time_left;
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic int __issue_avs_command(struct private_data *priv, unsigned int cmd,
22562306a36Sopenharmony_ci			       unsigned int num_in, unsigned int num_out,
22662306a36Sopenharmony_ci			       u32 args[])
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	void __iomem *base = priv->base;
22962306a36Sopenharmony_ci	unsigned long time_left;
23062306a36Sopenharmony_ci	unsigned int i;
23162306a36Sopenharmony_ci	int ret;
23262306a36Sopenharmony_ci	u32 val;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	ret = down_interruptible(&priv->sem);
23562306a36Sopenharmony_ci	if (ret)
23662306a36Sopenharmony_ci		return ret;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	/*
23962306a36Sopenharmony_ci	 * Make sure no other command is currently running: cmd is 0 if AVS
24062306a36Sopenharmony_ci	 * co-processor is idle. Due to the guard above, we should almost never
24162306a36Sopenharmony_ci	 * have to wait here.
24262306a36Sopenharmony_ci	 */
24362306a36Sopenharmony_ci	for (i = 0, val = 1; val != 0 && i < AVS_LOOP_LIMIT; i++)
24462306a36Sopenharmony_ci		val = readl(base + AVS_MBOX_COMMAND);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	/* Give the caller a chance to retry if AVS is busy. */
24762306a36Sopenharmony_ci	if (i == AVS_LOOP_LIMIT) {
24862306a36Sopenharmony_ci		ret = -EAGAIN;
24962306a36Sopenharmony_ci		goto out;
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	/* Clear status before we begin. */
25362306a36Sopenharmony_ci	writel(AVS_STATUS_CLEAR, base + AVS_MBOX_STATUS);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	/* Provide input parameters */
25662306a36Sopenharmony_ci	for (i = 0; i < num_in; i++)
25762306a36Sopenharmony_ci		writel(args[i], base + AVS_MBOX_PARAM(i));
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	/* Protect from spurious interrupts. */
26062306a36Sopenharmony_ci	reinit_completion(&priv->done);
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	/* Now issue the command & tell firmware to wake up to process it. */
26362306a36Sopenharmony_ci	writel(cmd, base + AVS_MBOX_COMMAND);
26462306a36Sopenharmony_ci	writel(AVS_CPU_L2_INT_MASK, priv->avs_intr_base + AVS_CPU_L2_SET0);
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	/* Wait for AVS co-processor to finish processing the command. */
26762306a36Sopenharmony_ci	time_left = wait_for_avs_command(priv, AVS_TIMEOUT);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	/*
27062306a36Sopenharmony_ci	 * If the AVS status is not in the expected range, it means AVS didn't
27162306a36Sopenharmony_ci	 * complete our command in time, and we return an error. Also, if there
27262306a36Sopenharmony_ci	 * is no "time left", we timed out waiting for the interrupt.
27362306a36Sopenharmony_ci	 */
27462306a36Sopenharmony_ci	val = readl(base + AVS_MBOX_STATUS);
27562306a36Sopenharmony_ci	if (time_left == 0 || val == 0 || val > AVS_STATUS_MAX) {
27662306a36Sopenharmony_ci		dev_err(priv->dev, "AVS command %#x didn't complete in time\n",
27762306a36Sopenharmony_ci			cmd);
27862306a36Sopenharmony_ci		dev_err(priv->dev, "    Time left: %u ms, AVS status: %#x\n",
27962306a36Sopenharmony_ci			jiffies_to_msecs(time_left), val);
28062306a36Sopenharmony_ci		ret = -ETIMEDOUT;
28162306a36Sopenharmony_ci		goto out;
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	/* Process returned values */
28562306a36Sopenharmony_ci	for (i = 0; i < num_out; i++)
28662306a36Sopenharmony_ci		args[i] = readl(base + AVS_MBOX_PARAM(i));
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	/* Clear status to tell AVS co-processor we are done. */
28962306a36Sopenharmony_ci	writel(AVS_STATUS_CLEAR, base + AVS_MBOX_STATUS);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	/* Convert firmware errors to errno's as much as possible. */
29262306a36Sopenharmony_ci	switch (val) {
29362306a36Sopenharmony_ci	case AVS_STATUS_INVALID:
29462306a36Sopenharmony_ci		ret = -EINVAL;
29562306a36Sopenharmony_ci		break;
29662306a36Sopenharmony_ci	case AVS_STATUS_NO_SUPP:
29762306a36Sopenharmony_ci		ret = -ENOTSUPP;
29862306a36Sopenharmony_ci		break;
29962306a36Sopenharmony_ci	case AVS_STATUS_NO_MAP:
30062306a36Sopenharmony_ci		ret = -ENOENT;
30162306a36Sopenharmony_ci		break;
30262306a36Sopenharmony_ci	case AVS_STATUS_MAP_SET:
30362306a36Sopenharmony_ci		ret = -EEXIST;
30462306a36Sopenharmony_ci		break;
30562306a36Sopenharmony_ci	case AVS_STATUS_FAILURE:
30662306a36Sopenharmony_ci		ret = -EIO;
30762306a36Sopenharmony_ci		break;
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ciout:
31162306a36Sopenharmony_ci	up(&priv->sem);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	return ret;
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_cistatic irqreturn_t irq_handler(int irq, void *data)
31762306a36Sopenharmony_ci{
31862306a36Sopenharmony_ci	struct private_data *priv = data;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	/* AVS command completed execution. Wake up __issue_avs_command(). */
32162306a36Sopenharmony_ci	complete(&priv->done);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	return IRQ_HANDLED;
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic char *brcm_avs_mode_to_string(unsigned int mode)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	switch (mode) {
32962306a36Sopenharmony_ci	case AVS_MODE_AVS:
33062306a36Sopenharmony_ci		return "AVS";
33162306a36Sopenharmony_ci	case AVS_MODE_DFS:
33262306a36Sopenharmony_ci		return "DFS";
33362306a36Sopenharmony_ci	case AVS_MODE_DVS:
33462306a36Sopenharmony_ci		return "DVS";
33562306a36Sopenharmony_ci	case AVS_MODE_DVFS:
33662306a36Sopenharmony_ci		return "DVFS";
33762306a36Sopenharmony_ci	}
33862306a36Sopenharmony_ci	return NULL;
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic void brcm_avs_parse_p1(u32 p1, unsigned int *mdiv_p0, unsigned int *pdiv,
34262306a36Sopenharmony_ci			      unsigned int *ndiv)
34362306a36Sopenharmony_ci{
34462306a36Sopenharmony_ci	*mdiv_p0 = (p1 >> MDIV_P0_SHIFT) & MDIV_P0_MASK;
34562306a36Sopenharmony_ci	*pdiv = (p1 >> PDIV_SHIFT) & PDIV_MASK;
34662306a36Sopenharmony_ci	*ndiv = (p1 >> NDIV_INT_SHIFT) & NDIV_INT_MASK;
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_cistatic void brcm_avs_parse_p2(u32 p2, unsigned int *mdiv_p1,
35062306a36Sopenharmony_ci			      unsigned int *mdiv_p2, unsigned int *mdiv_p3,
35162306a36Sopenharmony_ci			      unsigned int *mdiv_p4)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	*mdiv_p4 = (p2 >> MDIV_P4_SHIFT) & MDIV_P4_MASK;
35462306a36Sopenharmony_ci	*mdiv_p3 = (p2 >> MDIV_P3_SHIFT) & MDIV_P3_MASK;
35562306a36Sopenharmony_ci	*mdiv_p2 = (p2 >> MDIV_P2_SHIFT) & MDIV_P2_MASK;
35662306a36Sopenharmony_ci	*mdiv_p1 = (p2 >> MDIV_P1_SHIFT) & MDIV_P1_MASK;
35762306a36Sopenharmony_ci}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_cistatic int brcm_avs_get_pmap(struct private_data *priv, struct pmap *pmap)
36062306a36Sopenharmony_ci{
36162306a36Sopenharmony_ci	u32 args[AVS_MAX_CMD_ARGS];
36262306a36Sopenharmony_ci	int ret;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	ret = __issue_avs_command(priv, AVS_CMD_GET_PMAP, 0, 4, args);
36562306a36Sopenharmony_ci	if (ret || !pmap)
36662306a36Sopenharmony_ci		return ret;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	pmap->mode = args[0];
36962306a36Sopenharmony_ci	pmap->p1 = args[1];
37062306a36Sopenharmony_ci	pmap->p2 = args[2];
37162306a36Sopenharmony_ci	pmap->state = args[3];
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	return 0;
37462306a36Sopenharmony_ci}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cistatic int brcm_avs_set_pmap(struct private_data *priv, struct pmap *pmap)
37762306a36Sopenharmony_ci{
37862306a36Sopenharmony_ci	u32 args[AVS_MAX_CMD_ARGS];
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	args[0] = pmap->mode;
38162306a36Sopenharmony_ci	args[1] = pmap->p1;
38262306a36Sopenharmony_ci	args[2] = pmap->p2;
38362306a36Sopenharmony_ci	args[3] = pmap->state;
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	return __issue_avs_command(priv, AVS_CMD_SET_PMAP, 4, 0, args);
38662306a36Sopenharmony_ci}
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_cistatic int brcm_avs_get_pstate(struct private_data *priv, unsigned int *pstate)
38962306a36Sopenharmony_ci{
39062306a36Sopenharmony_ci	u32 args[AVS_MAX_CMD_ARGS];
39162306a36Sopenharmony_ci	int ret;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	ret = __issue_avs_command(priv, AVS_CMD_GET_PSTATE, 0, 1, args);
39462306a36Sopenharmony_ci	if (ret)
39562306a36Sopenharmony_ci		return ret;
39662306a36Sopenharmony_ci	*pstate = args[0];
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	return 0;
39962306a36Sopenharmony_ci}
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_cistatic int brcm_avs_set_pstate(struct private_data *priv, unsigned int pstate)
40262306a36Sopenharmony_ci{
40362306a36Sopenharmony_ci	u32 args[AVS_MAX_CMD_ARGS];
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	args[0] = pstate;
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	return __issue_avs_command(priv, AVS_CMD_SET_PSTATE, 1, 0, args);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci}
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cistatic u32 brcm_avs_get_voltage(void __iomem *base)
41262306a36Sopenharmony_ci{
41362306a36Sopenharmony_ci	return readl(base + AVS_MBOX_VOLTAGE1);
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic u32 brcm_avs_get_frequency(void __iomem *base)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	return readl(base + AVS_MBOX_FREQUENCY) * 1000;	/* in kHz */
41962306a36Sopenharmony_ci}
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci/*
42262306a36Sopenharmony_ci * We determine which frequencies are supported by cycling through all P-states
42362306a36Sopenharmony_ci * and reading back what frequency we are running at for each P-state.
42462306a36Sopenharmony_ci */
42562306a36Sopenharmony_cistatic struct cpufreq_frequency_table *
42662306a36Sopenharmony_cibrcm_avs_get_freq_table(struct device *dev, struct private_data *priv)
42762306a36Sopenharmony_ci{
42862306a36Sopenharmony_ci	struct cpufreq_frequency_table *table;
42962306a36Sopenharmony_ci	unsigned int pstate;
43062306a36Sopenharmony_ci	int i, ret;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	/* Remember P-state for later */
43362306a36Sopenharmony_ci	ret = brcm_avs_get_pstate(priv, &pstate);
43462306a36Sopenharmony_ci	if (ret)
43562306a36Sopenharmony_ci		return ERR_PTR(ret);
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	/*
43862306a36Sopenharmony_ci	 * We allocate space for the 5 different P-STATES AVS,
43962306a36Sopenharmony_ci	 * plus extra space for a terminating element.
44062306a36Sopenharmony_ci	 */
44162306a36Sopenharmony_ci	table = devm_kcalloc(dev, AVS_PSTATE_MAX + 1 + 1, sizeof(*table),
44262306a36Sopenharmony_ci			     GFP_KERNEL);
44362306a36Sopenharmony_ci	if (!table)
44462306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci	for (i = AVS_PSTATE_P0; i <= AVS_PSTATE_MAX; i++) {
44762306a36Sopenharmony_ci		ret = brcm_avs_set_pstate(priv, i);
44862306a36Sopenharmony_ci		if (ret)
44962306a36Sopenharmony_ci			return ERR_PTR(ret);
45062306a36Sopenharmony_ci		table[i].frequency = brcm_avs_get_frequency(priv->base);
45162306a36Sopenharmony_ci		table[i].driver_data = i;
45262306a36Sopenharmony_ci	}
45362306a36Sopenharmony_ci	table[i].frequency = CPUFREQ_TABLE_END;
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	/* Restore P-state */
45662306a36Sopenharmony_ci	ret = brcm_avs_set_pstate(priv, pstate);
45762306a36Sopenharmony_ci	if (ret)
45862306a36Sopenharmony_ci		return ERR_PTR(ret);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	return table;
46162306a36Sopenharmony_ci}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci/*
46462306a36Sopenharmony_ci * To ensure the right firmware is running we need to
46562306a36Sopenharmony_ci *    - check the MAGIC matches what we expect
46662306a36Sopenharmony_ci *    - brcm_avs_get_pmap() doesn't return -ENOTSUPP or -EINVAL
46762306a36Sopenharmony_ci * We need to set up our interrupt handling before calling brcm_avs_get_pmap()!
46862306a36Sopenharmony_ci */
46962306a36Sopenharmony_cistatic bool brcm_avs_is_firmware_loaded(struct private_data *priv)
47062306a36Sopenharmony_ci{
47162306a36Sopenharmony_ci	u32 magic;
47262306a36Sopenharmony_ci	int rc;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	rc = brcm_avs_get_pmap(priv, NULL);
47562306a36Sopenharmony_ci	magic = readl(priv->base + AVS_MBOX_MAGIC);
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	return (magic == AVS_FIRMWARE_MAGIC) && ((rc != -ENOTSUPP) ||
47862306a36Sopenharmony_ci		(rc != -EINVAL));
47962306a36Sopenharmony_ci}
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_cistatic unsigned int brcm_avs_cpufreq_get(unsigned int cpu)
48262306a36Sopenharmony_ci{
48362306a36Sopenharmony_ci	struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
48462306a36Sopenharmony_ci	if (!policy)
48562306a36Sopenharmony_ci		return 0;
48662306a36Sopenharmony_ci	struct private_data *priv = policy->driver_data;
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	cpufreq_cpu_put(policy);
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	return brcm_avs_get_frequency(priv->base);
49162306a36Sopenharmony_ci}
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_cistatic int brcm_avs_target_index(struct cpufreq_policy *policy,
49462306a36Sopenharmony_ci				 unsigned int index)
49562306a36Sopenharmony_ci{
49662306a36Sopenharmony_ci	return brcm_avs_set_pstate(policy->driver_data,
49762306a36Sopenharmony_ci				  policy->freq_table[index].driver_data);
49862306a36Sopenharmony_ci}
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_cistatic int brcm_avs_suspend(struct cpufreq_policy *policy)
50162306a36Sopenharmony_ci{
50262306a36Sopenharmony_ci	struct private_data *priv = policy->driver_data;
50362306a36Sopenharmony_ci	int ret;
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	ret = brcm_avs_get_pmap(priv, &priv->pmap);
50662306a36Sopenharmony_ci	if (ret)
50762306a36Sopenharmony_ci		return ret;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	/*
51062306a36Sopenharmony_ci	 * We can't use the P-state returned by brcm_avs_get_pmap(), since
51162306a36Sopenharmony_ci	 * that's the initial P-state from when the P-map was downloaded to the
51262306a36Sopenharmony_ci	 * AVS co-processor, not necessarily the P-state we are running at now.
51362306a36Sopenharmony_ci	 * So, we get the current P-state explicitly.
51462306a36Sopenharmony_ci	 */
51562306a36Sopenharmony_ci	ret = brcm_avs_get_pstate(priv, &priv->pmap.state);
51662306a36Sopenharmony_ci	if (ret)
51762306a36Sopenharmony_ci		return ret;
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	/* This is best effort. Nothing to do if it fails. */
52062306a36Sopenharmony_ci	(void)__issue_avs_command(priv, AVS_CMD_S2_ENTER, 0, 0, NULL);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	return 0;
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cistatic int brcm_avs_resume(struct cpufreq_policy *policy)
52662306a36Sopenharmony_ci{
52762306a36Sopenharmony_ci	struct private_data *priv = policy->driver_data;
52862306a36Sopenharmony_ci	int ret;
52962306a36Sopenharmony_ci
53062306a36Sopenharmony_ci	/* This is best effort. Nothing to do if it fails. */
53162306a36Sopenharmony_ci	(void)__issue_avs_command(priv, AVS_CMD_S2_EXIT, 0, 0, NULL);
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	ret = brcm_avs_set_pmap(priv, &priv->pmap);
53462306a36Sopenharmony_ci	if (ret == -EEXIST) {
53562306a36Sopenharmony_ci		struct platform_device *pdev  = cpufreq_get_driver_data();
53662306a36Sopenharmony_ci		struct device *dev = &pdev->dev;
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci		dev_warn(dev, "PMAP was already set\n");
53962306a36Sopenharmony_ci		ret = 0;
54062306a36Sopenharmony_ci	}
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	return ret;
54362306a36Sopenharmony_ci}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci/*
54662306a36Sopenharmony_ci * All initialization code that we only want to execute once goes here. Setup
54762306a36Sopenharmony_ci * code that can be re-tried on every core (if it failed before) can go into
54862306a36Sopenharmony_ci * brcm_avs_cpufreq_init().
54962306a36Sopenharmony_ci */
55062306a36Sopenharmony_cistatic int brcm_avs_prepare_init(struct platform_device *pdev)
55162306a36Sopenharmony_ci{
55262306a36Sopenharmony_ci	struct private_data *priv;
55362306a36Sopenharmony_ci	struct device *dev;
55462306a36Sopenharmony_ci	int ret;
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	dev = &pdev->dev;
55762306a36Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
55862306a36Sopenharmony_ci	if (!priv)
55962306a36Sopenharmony_ci		return -ENOMEM;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	priv->dev = dev;
56262306a36Sopenharmony_ci	sema_init(&priv->sem, 1);
56362306a36Sopenharmony_ci	init_completion(&priv->done);
56462306a36Sopenharmony_ci	platform_set_drvdata(pdev, priv);
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	priv->base = __map_region(BRCM_AVS_CPU_DATA);
56762306a36Sopenharmony_ci	if (!priv->base) {
56862306a36Sopenharmony_ci		dev_err(dev, "Couldn't find property %s in device tree.\n",
56962306a36Sopenharmony_ci			BRCM_AVS_CPU_DATA);
57062306a36Sopenharmony_ci		return -ENOENT;
57162306a36Sopenharmony_ci	}
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	priv->avs_intr_base = __map_region(BRCM_AVS_CPU_INTR);
57462306a36Sopenharmony_ci	if (!priv->avs_intr_base) {
57562306a36Sopenharmony_ci		dev_err(dev, "Couldn't find property %s in device tree.\n",
57662306a36Sopenharmony_ci			BRCM_AVS_CPU_INTR);
57762306a36Sopenharmony_ci		ret = -ENOENT;
57862306a36Sopenharmony_ci		goto unmap_base;
57962306a36Sopenharmony_ci	}
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	priv->host_irq = platform_get_irq_byname(pdev, BRCM_AVS_HOST_INTR);
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	ret = devm_request_irq(dev, priv->host_irq, irq_handler,
58462306a36Sopenharmony_ci			       IRQF_TRIGGER_RISING,
58562306a36Sopenharmony_ci			       BRCM_AVS_HOST_INTR, priv);
58662306a36Sopenharmony_ci	if (ret && priv->host_irq >= 0) {
58762306a36Sopenharmony_ci		dev_err(dev, "IRQ request failed: %s (%d) -- %d\n",
58862306a36Sopenharmony_ci			BRCM_AVS_HOST_INTR, priv->host_irq, ret);
58962306a36Sopenharmony_ci		goto unmap_intr_base;
59062306a36Sopenharmony_ci	}
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	if (brcm_avs_is_firmware_loaded(priv))
59362306a36Sopenharmony_ci		return 0;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	dev_err(dev, "AVS firmware is not loaded or doesn't support DVFS\n");
59662306a36Sopenharmony_ci	ret = -ENODEV;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ciunmap_intr_base:
59962306a36Sopenharmony_ci	iounmap(priv->avs_intr_base);
60062306a36Sopenharmony_ciunmap_base:
60162306a36Sopenharmony_ci	iounmap(priv->base);
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	return ret;
60462306a36Sopenharmony_ci}
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_cistatic void brcm_avs_prepare_uninit(struct platform_device *pdev)
60762306a36Sopenharmony_ci{
60862306a36Sopenharmony_ci	struct private_data *priv;
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	priv = platform_get_drvdata(pdev);
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	iounmap(priv->avs_intr_base);
61362306a36Sopenharmony_ci	iounmap(priv->base);
61462306a36Sopenharmony_ci}
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_cistatic int brcm_avs_cpufreq_init(struct cpufreq_policy *policy)
61762306a36Sopenharmony_ci{
61862306a36Sopenharmony_ci	struct cpufreq_frequency_table *freq_table;
61962306a36Sopenharmony_ci	struct platform_device *pdev;
62062306a36Sopenharmony_ci	struct private_data *priv;
62162306a36Sopenharmony_ci	struct device *dev;
62262306a36Sopenharmony_ci	int ret;
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	pdev = cpufreq_get_driver_data();
62562306a36Sopenharmony_ci	priv = platform_get_drvdata(pdev);
62662306a36Sopenharmony_ci	policy->driver_data = priv;
62762306a36Sopenharmony_ci	dev = &pdev->dev;
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	freq_table = brcm_avs_get_freq_table(dev, priv);
63062306a36Sopenharmony_ci	if (IS_ERR(freq_table)) {
63162306a36Sopenharmony_ci		ret = PTR_ERR(freq_table);
63262306a36Sopenharmony_ci		dev_err(dev, "Couldn't determine frequency table (%d).\n", ret);
63362306a36Sopenharmony_ci		return ret;
63462306a36Sopenharmony_ci	}
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci	policy->freq_table = freq_table;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	/* All cores share the same clock and thus the same policy. */
63962306a36Sopenharmony_ci	cpumask_setall(policy->cpus);
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	ret = __issue_avs_command(priv, AVS_CMD_ENABLE, 0, 0, NULL);
64262306a36Sopenharmony_ci	if (!ret) {
64362306a36Sopenharmony_ci		unsigned int pstate;
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci		ret = brcm_avs_get_pstate(priv, &pstate);
64662306a36Sopenharmony_ci		if (!ret) {
64762306a36Sopenharmony_ci			policy->cur = freq_table[pstate].frequency;
64862306a36Sopenharmony_ci			dev_info(dev, "registered\n");
64962306a36Sopenharmony_ci			return 0;
65062306a36Sopenharmony_ci		}
65162306a36Sopenharmony_ci	}
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	dev_err(dev, "couldn't initialize driver (%d)\n", ret);
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	return ret;
65662306a36Sopenharmony_ci}
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_cistatic ssize_t show_brcm_avs_pstate(struct cpufreq_policy *policy, char *buf)
65962306a36Sopenharmony_ci{
66062306a36Sopenharmony_ci	struct private_data *priv = policy->driver_data;
66162306a36Sopenharmony_ci	unsigned int pstate;
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci	if (brcm_avs_get_pstate(priv, &pstate))
66462306a36Sopenharmony_ci		return sprintf(buf, "<unknown>\n");
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	return sprintf(buf, "%u\n", pstate);
66762306a36Sopenharmony_ci}
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_cistatic ssize_t show_brcm_avs_mode(struct cpufreq_policy *policy, char *buf)
67062306a36Sopenharmony_ci{
67162306a36Sopenharmony_ci	struct private_data *priv = policy->driver_data;
67262306a36Sopenharmony_ci	struct pmap pmap;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	if (brcm_avs_get_pmap(priv, &pmap))
67562306a36Sopenharmony_ci		return sprintf(buf, "<unknown>\n");
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci	return sprintf(buf, "%s %u\n", brcm_avs_mode_to_string(pmap.mode),
67862306a36Sopenharmony_ci		pmap.mode);
67962306a36Sopenharmony_ci}
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_cistatic ssize_t show_brcm_avs_pmap(struct cpufreq_policy *policy, char *buf)
68262306a36Sopenharmony_ci{
68362306a36Sopenharmony_ci	unsigned int mdiv_p0, mdiv_p1, mdiv_p2, mdiv_p3, mdiv_p4;
68462306a36Sopenharmony_ci	struct private_data *priv = policy->driver_data;
68562306a36Sopenharmony_ci	unsigned int ndiv, pdiv;
68662306a36Sopenharmony_ci	struct pmap pmap;
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	if (brcm_avs_get_pmap(priv, &pmap))
68962306a36Sopenharmony_ci		return sprintf(buf, "<unknown>\n");
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	brcm_avs_parse_p1(pmap.p1, &mdiv_p0, &pdiv, &ndiv);
69262306a36Sopenharmony_ci	brcm_avs_parse_p2(pmap.p2, &mdiv_p1, &mdiv_p2, &mdiv_p3, &mdiv_p4);
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci	return sprintf(buf, "0x%08x 0x%08x %u %u %u %u %u %u %u %u %u\n",
69562306a36Sopenharmony_ci		pmap.p1, pmap.p2, ndiv, pdiv, mdiv_p0, mdiv_p1, mdiv_p2,
69662306a36Sopenharmony_ci		mdiv_p3, mdiv_p4, pmap.mode, pmap.state);
69762306a36Sopenharmony_ci}
69862306a36Sopenharmony_ci
69962306a36Sopenharmony_cistatic ssize_t show_brcm_avs_voltage(struct cpufreq_policy *policy, char *buf)
70062306a36Sopenharmony_ci{
70162306a36Sopenharmony_ci	struct private_data *priv = policy->driver_data;
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci	return sprintf(buf, "0x%08x\n", brcm_avs_get_voltage(priv->base));
70462306a36Sopenharmony_ci}
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_cistatic ssize_t show_brcm_avs_frequency(struct cpufreq_policy *policy, char *buf)
70762306a36Sopenharmony_ci{
70862306a36Sopenharmony_ci	struct private_data *priv = policy->driver_data;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci	return sprintf(buf, "0x%08x\n", brcm_avs_get_frequency(priv->base));
71162306a36Sopenharmony_ci}
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_cicpufreq_freq_attr_ro(brcm_avs_pstate);
71462306a36Sopenharmony_cicpufreq_freq_attr_ro(brcm_avs_mode);
71562306a36Sopenharmony_cicpufreq_freq_attr_ro(brcm_avs_pmap);
71662306a36Sopenharmony_cicpufreq_freq_attr_ro(brcm_avs_voltage);
71762306a36Sopenharmony_cicpufreq_freq_attr_ro(brcm_avs_frequency);
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_cistatic struct freq_attr *brcm_avs_cpufreq_attr[] = {
72062306a36Sopenharmony_ci	&cpufreq_freq_attr_scaling_available_freqs,
72162306a36Sopenharmony_ci	&brcm_avs_pstate,
72262306a36Sopenharmony_ci	&brcm_avs_mode,
72362306a36Sopenharmony_ci	&brcm_avs_pmap,
72462306a36Sopenharmony_ci	&brcm_avs_voltage,
72562306a36Sopenharmony_ci	&brcm_avs_frequency,
72662306a36Sopenharmony_ci	NULL
72762306a36Sopenharmony_ci};
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_cistatic struct cpufreq_driver brcm_avs_driver = {
73062306a36Sopenharmony_ci	.flags		= CPUFREQ_NEED_INITIAL_FREQ_CHECK,
73162306a36Sopenharmony_ci	.verify		= cpufreq_generic_frequency_table_verify,
73262306a36Sopenharmony_ci	.target_index	= brcm_avs_target_index,
73362306a36Sopenharmony_ci	.get		= brcm_avs_cpufreq_get,
73462306a36Sopenharmony_ci	.suspend	= brcm_avs_suspend,
73562306a36Sopenharmony_ci	.resume		= brcm_avs_resume,
73662306a36Sopenharmony_ci	.init		= brcm_avs_cpufreq_init,
73762306a36Sopenharmony_ci	.attr		= brcm_avs_cpufreq_attr,
73862306a36Sopenharmony_ci	.name		= BRCM_AVS_CPUFREQ_PREFIX,
73962306a36Sopenharmony_ci};
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_cistatic int brcm_avs_cpufreq_probe(struct platform_device *pdev)
74262306a36Sopenharmony_ci{
74362306a36Sopenharmony_ci	int ret;
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	ret = brcm_avs_prepare_init(pdev);
74662306a36Sopenharmony_ci	if (ret)
74762306a36Sopenharmony_ci		return ret;
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_ci	brcm_avs_driver.driver_data = pdev;
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci	ret = cpufreq_register_driver(&brcm_avs_driver);
75262306a36Sopenharmony_ci	if (ret)
75362306a36Sopenharmony_ci		brcm_avs_prepare_uninit(pdev);
75462306a36Sopenharmony_ci
75562306a36Sopenharmony_ci	return ret;
75662306a36Sopenharmony_ci}
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_cistatic void brcm_avs_cpufreq_remove(struct platform_device *pdev)
75962306a36Sopenharmony_ci{
76062306a36Sopenharmony_ci	cpufreq_unregister_driver(&brcm_avs_driver);
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	brcm_avs_prepare_uninit(pdev);
76362306a36Sopenharmony_ci}
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_cistatic const struct of_device_id brcm_avs_cpufreq_match[] = {
76662306a36Sopenharmony_ci	{ .compatible = BRCM_AVS_CPU_DATA },
76762306a36Sopenharmony_ci	{ }
76862306a36Sopenharmony_ci};
76962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, brcm_avs_cpufreq_match);
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_cistatic struct platform_driver brcm_avs_cpufreq_platdrv = {
77262306a36Sopenharmony_ci	.driver = {
77362306a36Sopenharmony_ci		.name	= BRCM_AVS_CPUFREQ_NAME,
77462306a36Sopenharmony_ci		.of_match_table = brcm_avs_cpufreq_match,
77562306a36Sopenharmony_ci	},
77662306a36Sopenharmony_ci	.probe		= brcm_avs_cpufreq_probe,
77762306a36Sopenharmony_ci	.remove_new	= brcm_avs_cpufreq_remove,
77862306a36Sopenharmony_ci};
77962306a36Sopenharmony_cimodule_platform_driver(brcm_avs_cpufreq_platdrv);
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ciMODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>");
78262306a36Sopenharmony_ciMODULE_DESCRIPTION("CPUfreq driver for Broadcom STB AVS");
78362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
784