18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * System Control and Power Interface (SCPI) Message Protocol driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * SCPI Message Protocol is used between the System Control Processor(SCP)
68c2ecf20Sopenharmony_ci * and the Application Processors(AP). The Message Handling Unit(MHU)
78c2ecf20Sopenharmony_ci * provides a mechanism for inter-processor communication between SCP's
88c2ecf20Sopenharmony_ci * Cortex M3 and AP.
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * SCP offers control and management of the core/cluster power states,
118c2ecf20Sopenharmony_ci * various power domain DVFS including the core/cluster, certain system
128c2ecf20Sopenharmony_ci * clocks configuration, thermal sensors and many others.
138c2ecf20Sopenharmony_ci *
148c2ecf20Sopenharmony_ci * Copyright (C) 2015 ARM Ltd.
158c2ecf20Sopenharmony_ci */
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <linux/bitmap.h>
208c2ecf20Sopenharmony_ci#include <linux/bitfield.h>
218c2ecf20Sopenharmony_ci#include <linux/device.h>
228c2ecf20Sopenharmony_ci#include <linux/err.h>
238c2ecf20Sopenharmony_ci#include <linux/export.h>
248c2ecf20Sopenharmony_ci#include <linux/io.h>
258c2ecf20Sopenharmony_ci#include <linux/kernel.h>
268c2ecf20Sopenharmony_ci#include <linux/list.h>
278c2ecf20Sopenharmony_ci#include <linux/mailbox_client.h>
288c2ecf20Sopenharmony_ci#include <linux/module.h>
298c2ecf20Sopenharmony_ci#include <linux/of_address.h>
308c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
318c2ecf20Sopenharmony_ci#include <linux/printk.h>
328c2ecf20Sopenharmony_ci#include <linux/pm_opp.h>
338c2ecf20Sopenharmony_ci#include <linux/scpi_protocol.h>
348c2ecf20Sopenharmony_ci#include <linux/slab.h>
358c2ecf20Sopenharmony_ci#include <linux/sort.h>
368c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#define CMD_ID_MASK		GENMASK(6, 0)
398c2ecf20Sopenharmony_ci#define CMD_TOKEN_ID_MASK	GENMASK(15, 8)
408c2ecf20Sopenharmony_ci#define CMD_DATA_SIZE_MASK	GENMASK(24, 16)
418c2ecf20Sopenharmony_ci#define CMD_LEGACY_DATA_SIZE_MASK	GENMASK(28, 20)
428c2ecf20Sopenharmony_ci#define PACK_SCPI_CMD(cmd_id, tx_sz)		\
438c2ecf20Sopenharmony_ci	(FIELD_PREP(CMD_ID_MASK, cmd_id) |	\
448c2ecf20Sopenharmony_ci	FIELD_PREP(CMD_DATA_SIZE_MASK, tx_sz))
458c2ecf20Sopenharmony_ci#define PACK_LEGACY_SCPI_CMD(cmd_id, tx_sz)	\
468c2ecf20Sopenharmony_ci	(FIELD_PREP(CMD_ID_MASK, cmd_id) |	\
478c2ecf20Sopenharmony_ci	FIELD_PREP(CMD_LEGACY_DATA_SIZE_MASK, tx_sz))
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci#define CMD_SIZE(cmd)	FIELD_GET(CMD_DATA_SIZE_MASK, cmd)
508c2ecf20Sopenharmony_ci#define CMD_UNIQ_MASK	(CMD_TOKEN_ID_MASK | CMD_ID_MASK)
518c2ecf20Sopenharmony_ci#define CMD_XTRACT_UNIQ(cmd)	((cmd) & CMD_UNIQ_MASK)
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci#define SCPI_SLOT		0
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci#define MAX_DVFS_DOMAINS	8
568c2ecf20Sopenharmony_ci#define MAX_DVFS_OPPS		16
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci#define PROTO_REV_MAJOR_MASK	GENMASK(31, 16)
598c2ecf20Sopenharmony_ci#define PROTO_REV_MINOR_MASK	GENMASK(15, 0)
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci#define FW_REV_MAJOR_MASK	GENMASK(31, 24)
628c2ecf20Sopenharmony_ci#define FW_REV_MINOR_MASK	GENMASK(23, 16)
638c2ecf20Sopenharmony_ci#define FW_REV_PATCH_MASK	GENMASK(15, 0)
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci#define MAX_RX_TIMEOUT		(msecs_to_jiffies(30))
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cienum scpi_error_codes {
688c2ecf20Sopenharmony_ci	SCPI_SUCCESS = 0, /* Success */
698c2ecf20Sopenharmony_ci	SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */
708c2ecf20Sopenharmony_ci	SCPI_ERR_ALIGN = 2, /* Invalid alignment */
718c2ecf20Sopenharmony_ci	SCPI_ERR_SIZE = 3, /* Invalid size */
728c2ecf20Sopenharmony_ci	SCPI_ERR_HANDLER = 4, /* Invalid handler/callback */
738c2ecf20Sopenharmony_ci	SCPI_ERR_ACCESS = 5, /* Invalid access/permission denied */
748c2ecf20Sopenharmony_ci	SCPI_ERR_RANGE = 6, /* Value out of range */
758c2ecf20Sopenharmony_ci	SCPI_ERR_TIMEOUT = 7, /* Timeout has occurred */
768c2ecf20Sopenharmony_ci	SCPI_ERR_NOMEM = 8, /* Invalid memory area or pointer */
778c2ecf20Sopenharmony_ci	SCPI_ERR_PWRSTATE = 9, /* Invalid power state */
788c2ecf20Sopenharmony_ci	SCPI_ERR_SUPPORT = 10, /* Not supported or disabled */
798c2ecf20Sopenharmony_ci	SCPI_ERR_DEVICE = 11, /* Device error */
808c2ecf20Sopenharmony_ci	SCPI_ERR_BUSY = 12, /* Device busy */
818c2ecf20Sopenharmony_ci	SCPI_ERR_MAX
828c2ecf20Sopenharmony_ci};
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci/* SCPI Standard commands */
858c2ecf20Sopenharmony_cienum scpi_std_cmd {
868c2ecf20Sopenharmony_ci	SCPI_CMD_INVALID		= 0x00,
878c2ecf20Sopenharmony_ci	SCPI_CMD_SCPI_READY		= 0x01,
888c2ecf20Sopenharmony_ci	SCPI_CMD_SCPI_CAPABILITIES	= 0x02,
898c2ecf20Sopenharmony_ci	SCPI_CMD_SET_CSS_PWR_STATE	= 0x03,
908c2ecf20Sopenharmony_ci	SCPI_CMD_GET_CSS_PWR_STATE	= 0x04,
918c2ecf20Sopenharmony_ci	SCPI_CMD_SET_SYS_PWR_STATE	= 0x05,
928c2ecf20Sopenharmony_ci	SCPI_CMD_SET_CPU_TIMER		= 0x06,
938c2ecf20Sopenharmony_ci	SCPI_CMD_CANCEL_CPU_TIMER	= 0x07,
948c2ecf20Sopenharmony_ci	SCPI_CMD_DVFS_CAPABILITIES	= 0x08,
958c2ecf20Sopenharmony_ci	SCPI_CMD_GET_DVFS_INFO		= 0x09,
968c2ecf20Sopenharmony_ci	SCPI_CMD_SET_DVFS		= 0x0a,
978c2ecf20Sopenharmony_ci	SCPI_CMD_GET_DVFS		= 0x0b,
988c2ecf20Sopenharmony_ci	SCPI_CMD_GET_DVFS_STAT		= 0x0c,
998c2ecf20Sopenharmony_ci	SCPI_CMD_CLOCK_CAPABILITIES	= 0x0d,
1008c2ecf20Sopenharmony_ci	SCPI_CMD_GET_CLOCK_INFO		= 0x0e,
1018c2ecf20Sopenharmony_ci	SCPI_CMD_SET_CLOCK_VALUE	= 0x0f,
1028c2ecf20Sopenharmony_ci	SCPI_CMD_GET_CLOCK_VALUE	= 0x10,
1038c2ecf20Sopenharmony_ci	SCPI_CMD_PSU_CAPABILITIES	= 0x11,
1048c2ecf20Sopenharmony_ci	SCPI_CMD_GET_PSU_INFO		= 0x12,
1058c2ecf20Sopenharmony_ci	SCPI_CMD_SET_PSU		= 0x13,
1068c2ecf20Sopenharmony_ci	SCPI_CMD_GET_PSU		= 0x14,
1078c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_CAPABILITIES	= 0x15,
1088c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_INFO		= 0x16,
1098c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_VALUE		= 0x17,
1108c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_CFG_PERIODIC	= 0x18,
1118c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_CFG_BOUNDS	= 0x19,
1128c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_ASYNC_VALUE	= 0x1a,
1138c2ecf20Sopenharmony_ci	SCPI_CMD_SET_DEVICE_PWR_STATE	= 0x1b,
1148c2ecf20Sopenharmony_ci	SCPI_CMD_GET_DEVICE_PWR_STATE	= 0x1c,
1158c2ecf20Sopenharmony_ci	SCPI_CMD_COUNT
1168c2ecf20Sopenharmony_ci};
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci/* SCPI Legacy Commands */
1198c2ecf20Sopenharmony_cienum legacy_scpi_std_cmd {
1208c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_INVALID			= 0x00,
1218c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SCPI_READY		= 0x01,
1228c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SCPI_CAPABILITIES	= 0x02,
1238c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_EVENT			= 0x03,
1248c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_CSS_PWR_STATE	= 0x04,
1258c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_CSS_PWR_STATE	= 0x05,
1268c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_CFG_PWR_STATE_STAT	= 0x06,
1278c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_PWR_STATE_STAT	= 0x07,
1288c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SYS_PWR_STATE		= 0x08,
1298c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_L2_READY		= 0x09,
1308c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_AP_TIMER		= 0x0a,
1318c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_CANCEL_AP_TIME		= 0x0b,
1328c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_DVFS_CAPABILITIES	= 0x0c,
1338c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_DVFS_INFO		= 0x0d,
1348c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_DVFS		= 0x0e,
1358c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_DVFS		= 0x0f,
1368c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_DVFS_STAT		= 0x10,
1378c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_RTC			= 0x11,
1388c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_RTC			= 0x12,
1398c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_CLOCK_CAPABILITIES	= 0x13,
1408c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_CLOCK_INDEX		= 0x14,
1418c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_CLOCK_VALUE		= 0x15,
1428c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_CLOCK_VALUE		= 0x16,
1438c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_PSU_CAPABILITIES	= 0x17,
1448c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_PSU			= 0x18,
1458c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_PSU			= 0x19,
1468c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_CAPABILITIES	= 0x1a,
1478c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_INFO		= 0x1b,
1488c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_VALUE		= 0x1c,
1498c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_CFG_PERIODIC	= 0x1d,
1508c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_CFG_BOUNDS	= 0x1e,
1518c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_ASYNC_VALUE	= 0x1f,
1528c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_COUNT
1538c2ecf20Sopenharmony_ci};
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci/* List all commands that are required to go through the high priority link */
1568c2ecf20Sopenharmony_cistatic int legacy_hpriority_cmds[] = {
1578c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_CSS_PWR_STATE,
1588c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_CFG_PWR_STATE_STAT,
1598c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_PWR_STATE_STAT,
1608c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_DVFS,
1618c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_DVFS,
1628c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_RTC,
1638c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_RTC,
1648c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_CLOCK_INDEX,
1658c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_CLOCK_VALUE,
1668c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_CLOCK_VALUE,
1678c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_PSU,
1688c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_PSU,
1698c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_CFG_PERIODIC,
1708c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_CFG_BOUNDS,
1718c2ecf20Sopenharmony_ci};
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci/* List all commands used by this driver, used as indexes */
1748c2ecf20Sopenharmony_cienum scpi_drv_cmds {
1758c2ecf20Sopenharmony_ci	CMD_SCPI_CAPABILITIES = 0,
1768c2ecf20Sopenharmony_ci	CMD_GET_CLOCK_INFO,
1778c2ecf20Sopenharmony_ci	CMD_GET_CLOCK_VALUE,
1788c2ecf20Sopenharmony_ci	CMD_SET_CLOCK_VALUE,
1798c2ecf20Sopenharmony_ci	CMD_GET_DVFS,
1808c2ecf20Sopenharmony_ci	CMD_SET_DVFS,
1818c2ecf20Sopenharmony_ci	CMD_GET_DVFS_INFO,
1828c2ecf20Sopenharmony_ci	CMD_SENSOR_CAPABILITIES,
1838c2ecf20Sopenharmony_ci	CMD_SENSOR_INFO,
1848c2ecf20Sopenharmony_ci	CMD_SENSOR_VALUE,
1858c2ecf20Sopenharmony_ci	CMD_SET_DEVICE_PWR_STATE,
1868c2ecf20Sopenharmony_ci	CMD_GET_DEVICE_PWR_STATE,
1878c2ecf20Sopenharmony_ci	CMD_MAX_COUNT,
1888c2ecf20Sopenharmony_ci};
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_cistatic int scpi_std_commands[CMD_MAX_COUNT] = {
1918c2ecf20Sopenharmony_ci	SCPI_CMD_SCPI_CAPABILITIES,
1928c2ecf20Sopenharmony_ci	SCPI_CMD_GET_CLOCK_INFO,
1938c2ecf20Sopenharmony_ci	SCPI_CMD_GET_CLOCK_VALUE,
1948c2ecf20Sopenharmony_ci	SCPI_CMD_SET_CLOCK_VALUE,
1958c2ecf20Sopenharmony_ci	SCPI_CMD_GET_DVFS,
1968c2ecf20Sopenharmony_ci	SCPI_CMD_SET_DVFS,
1978c2ecf20Sopenharmony_ci	SCPI_CMD_GET_DVFS_INFO,
1988c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_CAPABILITIES,
1998c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_INFO,
2008c2ecf20Sopenharmony_ci	SCPI_CMD_SENSOR_VALUE,
2018c2ecf20Sopenharmony_ci	SCPI_CMD_SET_DEVICE_PWR_STATE,
2028c2ecf20Sopenharmony_ci	SCPI_CMD_GET_DEVICE_PWR_STATE,
2038c2ecf20Sopenharmony_ci};
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic int scpi_legacy_commands[CMD_MAX_COUNT] = {
2068c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SCPI_CAPABILITIES,
2078c2ecf20Sopenharmony_ci	-1, /* GET_CLOCK_INFO */
2088c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_CLOCK_VALUE,
2098c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_CLOCK_VALUE,
2108c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_DVFS,
2118c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SET_DVFS,
2128c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_GET_DVFS_INFO,
2138c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_CAPABILITIES,
2148c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_INFO,
2158c2ecf20Sopenharmony_ci	LEGACY_SCPI_CMD_SENSOR_VALUE,
2168c2ecf20Sopenharmony_ci	-1, /* SET_DEVICE_PWR_STATE */
2178c2ecf20Sopenharmony_ci	-1, /* GET_DEVICE_PWR_STATE */
2188c2ecf20Sopenharmony_ci};
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_cistruct scpi_xfer {
2218c2ecf20Sopenharmony_ci	u32 slot; /* has to be first element */
2228c2ecf20Sopenharmony_ci	u32 cmd;
2238c2ecf20Sopenharmony_ci	u32 status;
2248c2ecf20Sopenharmony_ci	const void *tx_buf;
2258c2ecf20Sopenharmony_ci	void *rx_buf;
2268c2ecf20Sopenharmony_ci	unsigned int tx_len;
2278c2ecf20Sopenharmony_ci	unsigned int rx_len;
2288c2ecf20Sopenharmony_ci	struct list_head node;
2298c2ecf20Sopenharmony_ci	struct completion done;
2308c2ecf20Sopenharmony_ci};
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistruct scpi_chan {
2338c2ecf20Sopenharmony_ci	struct mbox_client cl;
2348c2ecf20Sopenharmony_ci	struct mbox_chan *chan;
2358c2ecf20Sopenharmony_ci	void __iomem *tx_payload;
2368c2ecf20Sopenharmony_ci	void __iomem *rx_payload;
2378c2ecf20Sopenharmony_ci	struct list_head rx_pending;
2388c2ecf20Sopenharmony_ci	struct list_head xfers_list;
2398c2ecf20Sopenharmony_ci	struct scpi_xfer *xfers;
2408c2ecf20Sopenharmony_ci	spinlock_t rx_lock; /* locking for the rx pending list */
2418c2ecf20Sopenharmony_ci	struct mutex xfers_lock;
2428c2ecf20Sopenharmony_ci	u8 token;
2438c2ecf20Sopenharmony_ci};
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_cistruct scpi_drvinfo {
2468c2ecf20Sopenharmony_ci	u32 protocol_version;
2478c2ecf20Sopenharmony_ci	u32 firmware_version;
2488c2ecf20Sopenharmony_ci	bool is_legacy;
2498c2ecf20Sopenharmony_ci	int num_chans;
2508c2ecf20Sopenharmony_ci	int *commands;
2518c2ecf20Sopenharmony_ci	DECLARE_BITMAP(cmd_priority, LEGACY_SCPI_CMD_COUNT);
2528c2ecf20Sopenharmony_ci	atomic_t next_chan;
2538c2ecf20Sopenharmony_ci	struct scpi_ops *scpi_ops;
2548c2ecf20Sopenharmony_ci	struct scpi_chan *channels;
2558c2ecf20Sopenharmony_ci	struct scpi_dvfs_info *dvfs[MAX_DVFS_DOMAINS];
2568c2ecf20Sopenharmony_ci};
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci/*
2598c2ecf20Sopenharmony_ci * The SCP firmware only executes in little-endian mode, so any buffers
2608c2ecf20Sopenharmony_ci * shared through SCPI should have their contents converted to little-endian
2618c2ecf20Sopenharmony_ci */
2628c2ecf20Sopenharmony_cistruct scpi_shared_mem {
2638c2ecf20Sopenharmony_ci	__le32 command;
2648c2ecf20Sopenharmony_ci	__le32 status;
2658c2ecf20Sopenharmony_ci	u8 payload[];
2668c2ecf20Sopenharmony_ci} __packed;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_cistruct legacy_scpi_shared_mem {
2698c2ecf20Sopenharmony_ci	__le32 status;
2708c2ecf20Sopenharmony_ci	u8 payload[];
2718c2ecf20Sopenharmony_ci} __packed;
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_cistruct scp_capabilities {
2748c2ecf20Sopenharmony_ci	__le32 protocol_version;
2758c2ecf20Sopenharmony_ci	__le32 event_version;
2768c2ecf20Sopenharmony_ci	__le32 platform_version;
2778c2ecf20Sopenharmony_ci	__le32 commands[4];
2788c2ecf20Sopenharmony_ci} __packed;
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_cistruct clk_get_info {
2818c2ecf20Sopenharmony_ci	__le16 id;
2828c2ecf20Sopenharmony_ci	__le16 flags;
2838c2ecf20Sopenharmony_ci	__le32 min_rate;
2848c2ecf20Sopenharmony_ci	__le32 max_rate;
2858c2ecf20Sopenharmony_ci	u8 name[20];
2868c2ecf20Sopenharmony_ci} __packed;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_cistruct clk_set_value {
2898c2ecf20Sopenharmony_ci	__le16 id;
2908c2ecf20Sopenharmony_ci	__le16 reserved;
2918c2ecf20Sopenharmony_ci	__le32 rate;
2928c2ecf20Sopenharmony_ci} __packed;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_cistruct legacy_clk_set_value {
2958c2ecf20Sopenharmony_ci	__le32 rate;
2968c2ecf20Sopenharmony_ci	__le16 id;
2978c2ecf20Sopenharmony_ci	__le16 reserved;
2988c2ecf20Sopenharmony_ci} __packed;
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_cistruct dvfs_info {
3018c2ecf20Sopenharmony_ci	u8 domain;
3028c2ecf20Sopenharmony_ci	u8 opp_count;
3038c2ecf20Sopenharmony_ci	__le16 latency;
3048c2ecf20Sopenharmony_ci	struct {
3058c2ecf20Sopenharmony_ci		__le32 freq;
3068c2ecf20Sopenharmony_ci		__le32 m_volt;
3078c2ecf20Sopenharmony_ci	} opps[MAX_DVFS_OPPS];
3088c2ecf20Sopenharmony_ci} __packed;
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_cistruct dvfs_set {
3118c2ecf20Sopenharmony_ci	u8 domain;
3128c2ecf20Sopenharmony_ci	u8 index;
3138c2ecf20Sopenharmony_ci} __packed;
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_cistruct _scpi_sensor_info {
3168c2ecf20Sopenharmony_ci	__le16 sensor_id;
3178c2ecf20Sopenharmony_ci	u8 class;
3188c2ecf20Sopenharmony_ci	u8 trigger_type;
3198c2ecf20Sopenharmony_ci	char name[20];
3208c2ecf20Sopenharmony_ci};
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_cistruct dev_pstate_set {
3238c2ecf20Sopenharmony_ci	__le16 dev_id;
3248c2ecf20Sopenharmony_ci	u8 pstate;
3258c2ecf20Sopenharmony_ci} __packed;
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_cistatic struct scpi_drvinfo *scpi_info;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_cistatic int scpi_linux_errmap[SCPI_ERR_MAX] = {
3308c2ecf20Sopenharmony_ci	/* better than switch case as long as return value is continuous */
3318c2ecf20Sopenharmony_ci	0, /* SCPI_SUCCESS */
3328c2ecf20Sopenharmony_ci	-EINVAL, /* SCPI_ERR_PARAM */
3338c2ecf20Sopenharmony_ci	-ENOEXEC, /* SCPI_ERR_ALIGN */
3348c2ecf20Sopenharmony_ci	-EMSGSIZE, /* SCPI_ERR_SIZE */
3358c2ecf20Sopenharmony_ci	-EINVAL, /* SCPI_ERR_HANDLER */
3368c2ecf20Sopenharmony_ci	-EACCES, /* SCPI_ERR_ACCESS */
3378c2ecf20Sopenharmony_ci	-ERANGE, /* SCPI_ERR_RANGE */
3388c2ecf20Sopenharmony_ci	-ETIMEDOUT, /* SCPI_ERR_TIMEOUT */
3398c2ecf20Sopenharmony_ci	-ENOMEM, /* SCPI_ERR_NOMEM */
3408c2ecf20Sopenharmony_ci	-EINVAL, /* SCPI_ERR_PWRSTATE */
3418c2ecf20Sopenharmony_ci	-EOPNOTSUPP, /* SCPI_ERR_SUPPORT */
3428c2ecf20Sopenharmony_ci	-EIO, /* SCPI_ERR_DEVICE */
3438c2ecf20Sopenharmony_ci	-EBUSY, /* SCPI_ERR_BUSY */
3448c2ecf20Sopenharmony_ci};
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_cistatic inline int scpi_to_linux_errno(int errno)
3478c2ecf20Sopenharmony_ci{
3488c2ecf20Sopenharmony_ci	if (errno >= SCPI_SUCCESS && errno < SCPI_ERR_MAX)
3498c2ecf20Sopenharmony_ci		return scpi_linux_errmap[errno];
3508c2ecf20Sopenharmony_ci	return -EIO;
3518c2ecf20Sopenharmony_ci}
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_cistatic void scpi_process_cmd(struct scpi_chan *ch, u32 cmd)
3548c2ecf20Sopenharmony_ci{
3558c2ecf20Sopenharmony_ci	unsigned long flags;
3568c2ecf20Sopenharmony_ci	struct scpi_xfer *t, *match = NULL;
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	spin_lock_irqsave(&ch->rx_lock, flags);
3598c2ecf20Sopenharmony_ci	if (list_empty(&ch->rx_pending)) {
3608c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&ch->rx_lock, flags);
3618c2ecf20Sopenharmony_ci		return;
3628c2ecf20Sopenharmony_ci	}
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	/* Command type is not replied by the SCP Firmware in legacy Mode
3658c2ecf20Sopenharmony_ci	 * We should consider that command is the head of pending RX commands
3668c2ecf20Sopenharmony_ci	 * if the list is not empty. In TX only mode, the list would be empty.
3678c2ecf20Sopenharmony_ci	 */
3688c2ecf20Sopenharmony_ci	if (scpi_info->is_legacy) {
3698c2ecf20Sopenharmony_ci		match = list_first_entry(&ch->rx_pending, struct scpi_xfer,
3708c2ecf20Sopenharmony_ci					 node);
3718c2ecf20Sopenharmony_ci		list_del(&match->node);
3728c2ecf20Sopenharmony_ci	} else {
3738c2ecf20Sopenharmony_ci		list_for_each_entry(t, &ch->rx_pending, node)
3748c2ecf20Sopenharmony_ci			if (CMD_XTRACT_UNIQ(t->cmd) == CMD_XTRACT_UNIQ(cmd)) {
3758c2ecf20Sopenharmony_ci				list_del(&t->node);
3768c2ecf20Sopenharmony_ci				match = t;
3778c2ecf20Sopenharmony_ci				break;
3788c2ecf20Sopenharmony_ci			}
3798c2ecf20Sopenharmony_ci	}
3808c2ecf20Sopenharmony_ci	/* check if wait_for_completion is in progress or timed-out */
3818c2ecf20Sopenharmony_ci	if (match && !completion_done(&match->done)) {
3828c2ecf20Sopenharmony_ci		unsigned int len;
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_ci		if (scpi_info->is_legacy) {
3858c2ecf20Sopenharmony_ci			struct legacy_scpi_shared_mem __iomem *mem =
3868c2ecf20Sopenharmony_ci							ch->rx_payload;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci			/* RX Length is not replied by the legacy Firmware */
3898c2ecf20Sopenharmony_ci			len = match->rx_len;
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci			match->status = ioread32(&mem->status);
3928c2ecf20Sopenharmony_ci			memcpy_fromio(match->rx_buf, mem->payload, len);
3938c2ecf20Sopenharmony_ci		} else {
3948c2ecf20Sopenharmony_ci			struct scpi_shared_mem __iomem *mem = ch->rx_payload;
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci			len = min_t(unsigned int, match->rx_len, CMD_SIZE(cmd));
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci			match->status = ioread32(&mem->status);
3998c2ecf20Sopenharmony_ci			memcpy_fromio(match->rx_buf, mem->payload, len);
4008c2ecf20Sopenharmony_ci		}
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci		if (match->rx_len > len)
4038c2ecf20Sopenharmony_ci			memset(match->rx_buf + len, 0, match->rx_len - len);
4048c2ecf20Sopenharmony_ci		complete(&match->done);
4058c2ecf20Sopenharmony_ci	}
4068c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&ch->rx_lock, flags);
4078c2ecf20Sopenharmony_ci}
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_cistatic void scpi_handle_remote_msg(struct mbox_client *c, void *msg)
4108c2ecf20Sopenharmony_ci{
4118c2ecf20Sopenharmony_ci	struct scpi_chan *ch = container_of(c, struct scpi_chan, cl);
4128c2ecf20Sopenharmony_ci	struct scpi_shared_mem __iomem *mem = ch->rx_payload;
4138c2ecf20Sopenharmony_ci	u32 cmd = 0;
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	if (!scpi_info->is_legacy)
4168c2ecf20Sopenharmony_ci		cmd = ioread32(&mem->command);
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci	scpi_process_cmd(ch, cmd);
4198c2ecf20Sopenharmony_ci}
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_cistatic void scpi_tx_prepare(struct mbox_client *c, void *msg)
4228c2ecf20Sopenharmony_ci{
4238c2ecf20Sopenharmony_ci	unsigned long flags;
4248c2ecf20Sopenharmony_ci	struct scpi_xfer *t = msg;
4258c2ecf20Sopenharmony_ci	struct scpi_chan *ch = container_of(c, struct scpi_chan, cl);
4268c2ecf20Sopenharmony_ci	struct scpi_shared_mem __iomem *mem = ch->tx_payload;
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci	if (t->tx_buf) {
4298c2ecf20Sopenharmony_ci		if (scpi_info->is_legacy)
4308c2ecf20Sopenharmony_ci			memcpy_toio(ch->tx_payload, t->tx_buf, t->tx_len);
4318c2ecf20Sopenharmony_ci		else
4328c2ecf20Sopenharmony_ci			memcpy_toio(mem->payload, t->tx_buf, t->tx_len);
4338c2ecf20Sopenharmony_ci	}
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci	if (t->rx_buf) {
4368c2ecf20Sopenharmony_ci		if (!(++ch->token))
4378c2ecf20Sopenharmony_ci			++ch->token;
4388c2ecf20Sopenharmony_ci		t->cmd |= FIELD_PREP(CMD_TOKEN_ID_MASK, ch->token);
4398c2ecf20Sopenharmony_ci		spin_lock_irqsave(&ch->rx_lock, flags);
4408c2ecf20Sopenharmony_ci		list_add_tail(&t->node, &ch->rx_pending);
4418c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&ch->rx_lock, flags);
4428c2ecf20Sopenharmony_ci	}
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	if (!scpi_info->is_legacy)
4458c2ecf20Sopenharmony_ci		iowrite32(t->cmd, &mem->command);
4468c2ecf20Sopenharmony_ci}
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_cistatic struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch)
4498c2ecf20Sopenharmony_ci{
4508c2ecf20Sopenharmony_ci	struct scpi_xfer *t;
4518c2ecf20Sopenharmony_ci
4528c2ecf20Sopenharmony_ci	mutex_lock(&ch->xfers_lock);
4538c2ecf20Sopenharmony_ci	if (list_empty(&ch->xfers_list)) {
4548c2ecf20Sopenharmony_ci		mutex_unlock(&ch->xfers_lock);
4558c2ecf20Sopenharmony_ci		return NULL;
4568c2ecf20Sopenharmony_ci	}
4578c2ecf20Sopenharmony_ci	t = list_first_entry(&ch->xfers_list, struct scpi_xfer, node);
4588c2ecf20Sopenharmony_ci	list_del(&t->node);
4598c2ecf20Sopenharmony_ci	mutex_unlock(&ch->xfers_lock);
4608c2ecf20Sopenharmony_ci	return t;
4618c2ecf20Sopenharmony_ci}
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_cistatic void put_scpi_xfer(struct scpi_xfer *t, struct scpi_chan *ch)
4648c2ecf20Sopenharmony_ci{
4658c2ecf20Sopenharmony_ci	mutex_lock(&ch->xfers_lock);
4668c2ecf20Sopenharmony_ci	list_add_tail(&t->node, &ch->xfers_list);
4678c2ecf20Sopenharmony_ci	mutex_unlock(&ch->xfers_lock);
4688c2ecf20Sopenharmony_ci}
4698c2ecf20Sopenharmony_ci
4708c2ecf20Sopenharmony_cistatic int scpi_send_message(u8 idx, void *tx_buf, unsigned int tx_len,
4718c2ecf20Sopenharmony_ci			     void *rx_buf, unsigned int rx_len)
4728c2ecf20Sopenharmony_ci{
4738c2ecf20Sopenharmony_ci	int ret;
4748c2ecf20Sopenharmony_ci	u8 chan;
4758c2ecf20Sopenharmony_ci	u8 cmd;
4768c2ecf20Sopenharmony_ci	struct scpi_xfer *msg;
4778c2ecf20Sopenharmony_ci	struct scpi_chan *scpi_chan;
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	if (scpi_info->commands[idx] < 0)
4808c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_ci	cmd = scpi_info->commands[idx];
4838c2ecf20Sopenharmony_ci
4848c2ecf20Sopenharmony_ci	if (scpi_info->is_legacy)
4858c2ecf20Sopenharmony_ci		chan = test_bit(cmd, scpi_info->cmd_priority) ? 1 : 0;
4868c2ecf20Sopenharmony_ci	else
4878c2ecf20Sopenharmony_ci		chan = atomic_inc_return(&scpi_info->next_chan) %
4888c2ecf20Sopenharmony_ci			scpi_info->num_chans;
4898c2ecf20Sopenharmony_ci	scpi_chan = scpi_info->channels + chan;
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_ci	msg = get_scpi_xfer(scpi_chan);
4928c2ecf20Sopenharmony_ci	if (!msg)
4938c2ecf20Sopenharmony_ci		return -ENOMEM;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	if (scpi_info->is_legacy) {
4968c2ecf20Sopenharmony_ci		msg->cmd = PACK_LEGACY_SCPI_CMD(cmd, tx_len);
4978c2ecf20Sopenharmony_ci		msg->slot = msg->cmd;
4988c2ecf20Sopenharmony_ci	} else {
4998c2ecf20Sopenharmony_ci		msg->slot = BIT(SCPI_SLOT);
5008c2ecf20Sopenharmony_ci		msg->cmd = PACK_SCPI_CMD(cmd, tx_len);
5018c2ecf20Sopenharmony_ci	}
5028c2ecf20Sopenharmony_ci	msg->tx_buf = tx_buf;
5038c2ecf20Sopenharmony_ci	msg->tx_len = tx_len;
5048c2ecf20Sopenharmony_ci	msg->rx_buf = rx_buf;
5058c2ecf20Sopenharmony_ci	msg->rx_len = rx_len;
5068c2ecf20Sopenharmony_ci	reinit_completion(&msg->done);
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	ret = mbox_send_message(scpi_chan->chan, msg);
5098c2ecf20Sopenharmony_ci	if (ret < 0 || !rx_buf)
5108c2ecf20Sopenharmony_ci		goto out;
5118c2ecf20Sopenharmony_ci
5128c2ecf20Sopenharmony_ci	if (!wait_for_completion_timeout(&msg->done, MAX_RX_TIMEOUT))
5138c2ecf20Sopenharmony_ci		ret = -ETIMEDOUT;
5148c2ecf20Sopenharmony_ci	else
5158c2ecf20Sopenharmony_ci		/* first status word */
5168c2ecf20Sopenharmony_ci		ret = msg->status;
5178c2ecf20Sopenharmony_ciout:
5188c2ecf20Sopenharmony_ci	if (ret < 0 && rx_buf) /* remove entry from the list if timed-out */
5198c2ecf20Sopenharmony_ci		scpi_process_cmd(scpi_chan, msg->cmd);
5208c2ecf20Sopenharmony_ci
5218c2ecf20Sopenharmony_ci	put_scpi_xfer(msg, scpi_chan);
5228c2ecf20Sopenharmony_ci	/* SCPI error codes > 0, translate them to Linux scale*/
5238c2ecf20Sopenharmony_ci	return ret > 0 ? scpi_to_linux_errno(ret) : ret;
5248c2ecf20Sopenharmony_ci}
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_cistatic u32 scpi_get_version(void)
5278c2ecf20Sopenharmony_ci{
5288c2ecf20Sopenharmony_ci	return scpi_info->protocol_version;
5298c2ecf20Sopenharmony_ci}
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_cistatic int
5328c2ecf20Sopenharmony_ciscpi_clk_get_range(u16 clk_id, unsigned long *min, unsigned long *max)
5338c2ecf20Sopenharmony_ci{
5348c2ecf20Sopenharmony_ci	int ret;
5358c2ecf20Sopenharmony_ci	struct clk_get_info clk;
5368c2ecf20Sopenharmony_ci	__le16 le_clk_id = cpu_to_le16(clk_id);
5378c2ecf20Sopenharmony_ci
5388c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_GET_CLOCK_INFO, &le_clk_id,
5398c2ecf20Sopenharmony_ci				sizeof(le_clk_id), &clk, sizeof(clk));
5408c2ecf20Sopenharmony_ci	if (!ret) {
5418c2ecf20Sopenharmony_ci		*min = le32_to_cpu(clk.min_rate);
5428c2ecf20Sopenharmony_ci		*max = le32_to_cpu(clk.max_rate);
5438c2ecf20Sopenharmony_ci	}
5448c2ecf20Sopenharmony_ci	return ret;
5458c2ecf20Sopenharmony_ci}
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_cistatic unsigned long scpi_clk_get_val(u16 clk_id)
5488c2ecf20Sopenharmony_ci{
5498c2ecf20Sopenharmony_ci	int ret;
5508c2ecf20Sopenharmony_ci	__le32 rate;
5518c2ecf20Sopenharmony_ci	__le16 le_clk_id = cpu_to_le16(clk_id);
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_GET_CLOCK_VALUE, &le_clk_id,
5548c2ecf20Sopenharmony_ci				sizeof(le_clk_id), &rate, sizeof(rate));
5558c2ecf20Sopenharmony_ci	if (ret)
5568c2ecf20Sopenharmony_ci		return 0;
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci	return le32_to_cpu(rate);
5598c2ecf20Sopenharmony_ci}
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_cistatic int scpi_clk_set_val(u16 clk_id, unsigned long rate)
5628c2ecf20Sopenharmony_ci{
5638c2ecf20Sopenharmony_ci	int stat;
5648c2ecf20Sopenharmony_ci	struct clk_set_value clk = {
5658c2ecf20Sopenharmony_ci		.id = cpu_to_le16(clk_id),
5668c2ecf20Sopenharmony_ci		.rate = cpu_to_le32(rate)
5678c2ecf20Sopenharmony_ci	};
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci	return scpi_send_message(CMD_SET_CLOCK_VALUE, &clk, sizeof(clk),
5708c2ecf20Sopenharmony_ci				 &stat, sizeof(stat));
5718c2ecf20Sopenharmony_ci}
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_cistatic int legacy_scpi_clk_set_val(u16 clk_id, unsigned long rate)
5748c2ecf20Sopenharmony_ci{
5758c2ecf20Sopenharmony_ci	int stat;
5768c2ecf20Sopenharmony_ci	struct legacy_clk_set_value clk = {
5778c2ecf20Sopenharmony_ci		.id = cpu_to_le16(clk_id),
5788c2ecf20Sopenharmony_ci		.rate = cpu_to_le32(rate)
5798c2ecf20Sopenharmony_ci	};
5808c2ecf20Sopenharmony_ci
5818c2ecf20Sopenharmony_ci	return scpi_send_message(CMD_SET_CLOCK_VALUE, &clk, sizeof(clk),
5828c2ecf20Sopenharmony_ci				 &stat, sizeof(stat));
5838c2ecf20Sopenharmony_ci}
5848c2ecf20Sopenharmony_ci
5858c2ecf20Sopenharmony_cistatic int scpi_dvfs_get_idx(u8 domain)
5868c2ecf20Sopenharmony_ci{
5878c2ecf20Sopenharmony_ci	int ret;
5888c2ecf20Sopenharmony_ci	u8 dvfs_idx;
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_GET_DVFS, &domain, sizeof(domain),
5918c2ecf20Sopenharmony_ci				&dvfs_idx, sizeof(dvfs_idx));
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_ci	return ret ? ret : dvfs_idx;
5948c2ecf20Sopenharmony_ci}
5958c2ecf20Sopenharmony_ci
5968c2ecf20Sopenharmony_cistatic int scpi_dvfs_set_idx(u8 domain, u8 index)
5978c2ecf20Sopenharmony_ci{
5988c2ecf20Sopenharmony_ci	int stat;
5998c2ecf20Sopenharmony_ci	struct dvfs_set dvfs = {domain, index};
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	return scpi_send_message(CMD_SET_DVFS, &dvfs, sizeof(dvfs),
6028c2ecf20Sopenharmony_ci				 &stat, sizeof(stat));
6038c2ecf20Sopenharmony_ci}
6048c2ecf20Sopenharmony_ci
6058c2ecf20Sopenharmony_cistatic int opp_cmp_func(const void *opp1, const void *opp2)
6068c2ecf20Sopenharmony_ci{
6078c2ecf20Sopenharmony_ci	const struct scpi_opp *t1 = opp1, *t2 = opp2;
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci	return t1->freq - t2->freq;
6108c2ecf20Sopenharmony_ci}
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_cistatic struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain)
6138c2ecf20Sopenharmony_ci{
6148c2ecf20Sopenharmony_ci	struct scpi_dvfs_info *info;
6158c2ecf20Sopenharmony_ci	struct scpi_opp *opp;
6168c2ecf20Sopenharmony_ci	struct dvfs_info buf;
6178c2ecf20Sopenharmony_ci	int ret, i;
6188c2ecf20Sopenharmony_ci
6198c2ecf20Sopenharmony_ci	if (domain >= MAX_DVFS_DOMAINS)
6208c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
6218c2ecf20Sopenharmony_ci
6228c2ecf20Sopenharmony_ci	if (scpi_info->dvfs[domain])	/* data already populated */
6238c2ecf20Sopenharmony_ci		return scpi_info->dvfs[domain];
6248c2ecf20Sopenharmony_ci
6258c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_GET_DVFS_INFO, &domain, sizeof(domain),
6268c2ecf20Sopenharmony_ci				&buf, sizeof(buf));
6278c2ecf20Sopenharmony_ci	if (ret)
6288c2ecf20Sopenharmony_ci		return ERR_PTR(ret);
6298c2ecf20Sopenharmony_ci
6308c2ecf20Sopenharmony_ci	info = kmalloc(sizeof(*info), GFP_KERNEL);
6318c2ecf20Sopenharmony_ci	if (!info)
6328c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
6338c2ecf20Sopenharmony_ci
6348c2ecf20Sopenharmony_ci	info->count = buf.opp_count;
6358c2ecf20Sopenharmony_ci	info->latency = le16_to_cpu(buf.latency) * 1000; /* uS to nS */
6368c2ecf20Sopenharmony_ci
6378c2ecf20Sopenharmony_ci	info->opps = kcalloc(info->count, sizeof(*opp), GFP_KERNEL);
6388c2ecf20Sopenharmony_ci	if (!info->opps) {
6398c2ecf20Sopenharmony_ci		kfree(info);
6408c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
6418c2ecf20Sopenharmony_ci	}
6428c2ecf20Sopenharmony_ci
6438c2ecf20Sopenharmony_ci	for (i = 0, opp = info->opps; i < info->count; i++, opp++) {
6448c2ecf20Sopenharmony_ci		opp->freq = le32_to_cpu(buf.opps[i].freq);
6458c2ecf20Sopenharmony_ci		opp->m_volt = le32_to_cpu(buf.opps[i].m_volt);
6468c2ecf20Sopenharmony_ci	}
6478c2ecf20Sopenharmony_ci
6488c2ecf20Sopenharmony_ci	sort(info->opps, info->count, sizeof(*opp), opp_cmp_func, NULL);
6498c2ecf20Sopenharmony_ci
6508c2ecf20Sopenharmony_ci	scpi_info->dvfs[domain] = info;
6518c2ecf20Sopenharmony_ci	return info;
6528c2ecf20Sopenharmony_ci}
6538c2ecf20Sopenharmony_ci
6548c2ecf20Sopenharmony_cistatic int scpi_dev_domain_id(struct device *dev)
6558c2ecf20Sopenharmony_ci{
6568c2ecf20Sopenharmony_ci	struct of_phandle_args clkspec;
6578c2ecf20Sopenharmony_ci
6588c2ecf20Sopenharmony_ci	if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells",
6598c2ecf20Sopenharmony_ci				       0, &clkspec))
6608c2ecf20Sopenharmony_ci		return -EINVAL;
6618c2ecf20Sopenharmony_ci
6628c2ecf20Sopenharmony_ci	return clkspec.args[0];
6638c2ecf20Sopenharmony_ci}
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_cistatic struct scpi_dvfs_info *scpi_dvfs_info(struct device *dev)
6668c2ecf20Sopenharmony_ci{
6678c2ecf20Sopenharmony_ci	int domain = scpi_dev_domain_id(dev);
6688c2ecf20Sopenharmony_ci
6698c2ecf20Sopenharmony_ci	if (domain < 0)
6708c2ecf20Sopenharmony_ci		return ERR_PTR(domain);
6718c2ecf20Sopenharmony_ci
6728c2ecf20Sopenharmony_ci	return scpi_dvfs_get_info(domain);
6738c2ecf20Sopenharmony_ci}
6748c2ecf20Sopenharmony_ci
6758c2ecf20Sopenharmony_cistatic int scpi_dvfs_get_transition_latency(struct device *dev)
6768c2ecf20Sopenharmony_ci{
6778c2ecf20Sopenharmony_ci	struct scpi_dvfs_info *info = scpi_dvfs_info(dev);
6788c2ecf20Sopenharmony_ci
6798c2ecf20Sopenharmony_ci	if (IS_ERR(info))
6808c2ecf20Sopenharmony_ci		return PTR_ERR(info);
6818c2ecf20Sopenharmony_ci
6828c2ecf20Sopenharmony_ci	return info->latency;
6838c2ecf20Sopenharmony_ci}
6848c2ecf20Sopenharmony_ci
6858c2ecf20Sopenharmony_cistatic int scpi_dvfs_add_opps_to_device(struct device *dev)
6868c2ecf20Sopenharmony_ci{
6878c2ecf20Sopenharmony_ci	int idx, ret;
6888c2ecf20Sopenharmony_ci	struct scpi_opp *opp;
6898c2ecf20Sopenharmony_ci	struct scpi_dvfs_info *info = scpi_dvfs_info(dev);
6908c2ecf20Sopenharmony_ci
6918c2ecf20Sopenharmony_ci	if (IS_ERR(info))
6928c2ecf20Sopenharmony_ci		return PTR_ERR(info);
6938c2ecf20Sopenharmony_ci
6948c2ecf20Sopenharmony_ci	if (!info->opps)
6958c2ecf20Sopenharmony_ci		return -EIO;
6968c2ecf20Sopenharmony_ci
6978c2ecf20Sopenharmony_ci	for (opp = info->opps, idx = 0; idx < info->count; idx++, opp++) {
6988c2ecf20Sopenharmony_ci		ret = dev_pm_opp_add(dev, opp->freq, opp->m_volt * 1000);
6998c2ecf20Sopenharmony_ci		if (ret) {
7008c2ecf20Sopenharmony_ci			dev_warn(dev, "failed to add opp %uHz %umV\n",
7018c2ecf20Sopenharmony_ci				 opp->freq, opp->m_volt);
7028c2ecf20Sopenharmony_ci			while (idx-- > 0)
7038c2ecf20Sopenharmony_ci				dev_pm_opp_remove(dev, (--opp)->freq);
7048c2ecf20Sopenharmony_ci			return ret;
7058c2ecf20Sopenharmony_ci		}
7068c2ecf20Sopenharmony_ci	}
7078c2ecf20Sopenharmony_ci	return 0;
7088c2ecf20Sopenharmony_ci}
7098c2ecf20Sopenharmony_ci
7108c2ecf20Sopenharmony_cistatic int scpi_sensor_get_capability(u16 *sensors)
7118c2ecf20Sopenharmony_ci{
7128c2ecf20Sopenharmony_ci	__le16 cap;
7138c2ecf20Sopenharmony_ci	int ret;
7148c2ecf20Sopenharmony_ci
7158c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_SENSOR_CAPABILITIES, NULL, 0, &cap,
7168c2ecf20Sopenharmony_ci				sizeof(cap));
7178c2ecf20Sopenharmony_ci	if (!ret)
7188c2ecf20Sopenharmony_ci		*sensors = le16_to_cpu(cap);
7198c2ecf20Sopenharmony_ci
7208c2ecf20Sopenharmony_ci	return ret;
7218c2ecf20Sopenharmony_ci}
7228c2ecf20Sopenharmony_ci
7238c2ecf20Sopenharmony_cistatic int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info)
7248c2ecf20Sopenharmony_ci{
7258c2ecf20Sopenharmony_ci	__le16 id = cpu_to_le16(sensor_id);
7268c2ecf20Sopenharmony_ci	struct _scpi_sensor_info _info;
7278c2ecf20Sopenharmony_ci	int ret;
7288c2ecf20Sopenharmony_ci
7298c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_SENSOR_INFO, &id, sizeof(id),
7308c2ecf20Sopenharmony_ci				&_info, sizeof(_info));
7318c2ecf20Sopenharmony_ci	if (!ret) {
7328c2ecf20Sopenharmony_ci		memcpy(info, &_info, sizeof(*info));
7338c2ecf20Sopenharmony_ci		info->sensor_id = le16_to_cpu(_info.sensor_id);
7348c2ecf20Sopenharmony_ci	}
7358c2ecf20Sopenharmony_ci
7368c2ecf20Sopenharmony_ci	return ret;
7378c2ecf20Sopenharmony_ci}
7388c2ecf20Sopenharmony_ci
7398c2ecf20Sopenharmony_cistatic int scpi_sensor_get_value(u16 sensor, u64 *val)
7408c2ecf20Sopenharmony_ci{
7418c2ecf20Sopenharmony_ci	__le16 id = cpu_to_le16(sensor);
7428c2ecf20Sopenharmony_ci	__le64 value;
7438c2ecf20Sopenharmony_ci	int ret;
7448c2ecf20Sopenharmony_ci
7458c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_SENSOR_VALUE, &id, sizeof(id),
7468c2ecf20Sopenharmony_ci				&value, sizeof(value));
7478c2ecf20Sopenharmony_ci	if (ret)
7488c2ecf20Sopenharmony_ci		return ret;
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_ci	if (scpi_info->is_legacy)
7518c2ecf20Sopenharmony_ci		/* only 32-bits supported, upper 32 bits can be junk */
7528c2ecf20Sopenharmony_ci		*val = le32_to_cpup((__le32 *)&value);
7538c2ecf20Sopenharmony_ci	else
7548c2ecf20Sopenharmony_ci		*val = le64_to_cpu(value);
7558c2ecf20Sopenharmony_ci
7568c2ecf20Sopenharmony_ci	return 0;
7578c2ecf20Sopenharmony_ci}
7588c2ecf20Sopenharmony_ci
7598c2ecf20Sopenharmony_cistatic int scpi_device_get_power_state(u16 dev_id)
7608c2ecf20Sopenharmony_ci{
7618c2ecf20Sopenharmony_ci	int ret;
7628c2ecf20Sopenharmony_ci	u8 pstate;
7638c2ecf20Sopenharmony_ci	__le16 id = cpu_to_le16(dev_id);
7648c2ecf20Sopenharmony_ci
7658c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_GET_DEVICE_PWR_STATE, &id,
7668c2ecf20Sopenharmony_ci				sizeof(id), &pstate, sizeof(pstate));
7678c2ecf20Sopenharmony_ci	return ret ? ret : pstate;
7688c2ecf20Sopenharmony_ci}
7698c2ecf20Sopenharmony_ci
7708c2ecf20Sopenharmony_cistatic int scpi_device_set_power_state(u16 dev_id, u8 pstate)
7718c2ecf20Sopenharmony_ci{
7728c2ecf20Sopenharmony_ci	int stat;
7738c2ecf20Sopenharmony_ci	struct dev_pstate_set dev_set = {
7748c2ecf20Sopenharmony_ci		.dev_id = cpu_to_le16(dev_id),
7758c2ecf20Sopenharmony_ci		.pstate = pstate,
7768c2ecf20Sopenharmony_ci	};
7778c2ecf20Sopenharmony_ci
7788c2ecf20Sopenharmony_ci	return scpi_send_message(CMD_SET_DEVICE_PWR_STATE, &dev_set,
7798c2ecf20Sopenharmony_ci				 sizeof(dev_set), &stat, sizeof(stat));
7808c2ecf20Sopenharmony_ci}
7818c2ecf20Sopenharmony_ci
7828c2ecf20Sopenharmony_cistatic struct scpi_ops scpi_ops = {
7838c2ecf20Sopenharmony_ci	.get_version = scpi_get_version,
7848c2ecf20Sopenharmony_ci	.clk_get_range = scpi_clk_get_range,
7858c2ecf20Sopenharmony_ci	.clk_get_val = scpi_clk_get_val,
7868c2ecf20Sopenharmony_ci	.clk_set_val = scpi_clk_set_val,
7878c2ecf20Sopenharmony_ci	.dvfs_get_idx = scpi_dvfs_get_idx,
7888c2ecf20Sopenharmony_ci	.dvfs_set_idx = scpi_dvfs_set_idx,
7898c2ecf20Sopenharmony_ci	.dvfs_get_info = scpi_dvfs_get_info,
7908c2ecf20Sopenharmony_ci	.device_domain_id = scpi_dev_domain_id,
7918c2ecf20Sopenharmony_ci	.get_transition_latency = scpi_dvfs_get_transition_latency,
7928c2ecf20Sopenharmony_ci	.add_opps_to_device = scpi_dvfs_add_opps_to_device,
7938c2ecf20Sopenharmony_ci	.sensor_get_capability = scpi_sensor_get_capability,
7948c2ecf20Sopenharmony_ci	.sensor_get_info = scpi_sensor_get_info,
7958c2ecf20Sopenharmony_ci	.sensor_get_value = scpi_sensor_get_value,
7968c2ecf20Sopenharmony_ci	.device_get_power_state = scpi_device_get_power_state,
7978c2ecf20Sopenharmony_ci	.device_set_power_state = scpi_device_set_power_state,
7988c2ecf20Sopenharmony_ci};
7998c2ecf20Sopenharmony_ci
8008c2ecf20Sopenharmony_cistruct scpi_ops *get_scpi_ops(void)
8018c2ecf20Sopenharmony_ci{
8028c2ecf20Sopenharmony_ci	return scpi_info ? scpi_info->scpi_ops : NULL;
8038c2ecf20Sopenharmony_ci}
8048c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(get_scpi_ops);
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_cistatic int scpi_init_versions(struct scpi_drvinfo *info)
8078c2ecf20Sopenharmony_ci{
8088c2ecf20Sopenharmony_ci	int ret;
8098c2ecf20Sopenharmony_ci	struct scp_capabilities caps;
8108c2ecf20Sopenharmony_ci
8118c2ecf20Sopenharmony_ci	ret = scpi_send_message(CMD_SCPI_CAPABILITIES, NULL, 0,
8128c2ecf20Sopenharmony_ci				&caps, sizeof(caps));
8138c2ecf20Sopenharmony_ci	if (!ret) {
8148c2ecf20Sopenharmony_ci		info->protocol_version = le32_to_cpu(caps.protocol_version);
8158c2ecf20Sopenharmony_ci		info->firmware_version = le32_to_cpu(caps.platform_version);
8168c2ecf20Sopenharmony_ci	}
8178c2ecf20Sopenharmony_ci	/* Ignore error if not implemented */
8188c2ecf20Sopenharmony_ci	if (info->is_legacy && ret == -EOPNOTSUPP)
8198c2ecf20Sopenharmony_ci		return 0;
8208c2ecf20Sopenharmony_ci
8218c2ecf20Sopenharmony_ci	return ret;
8228c2ecf20Sopenharmony_ci}
8238c2ecf20Sopenharmony_ci
8248c2ecf20Sopenharmony_cistatic ssize_t protocol_version_show(struct device *dev,
8258c2ecf20Sopenharmony_ci				     struct device_attribute *attr, char *buf)
8268c2ecf20Sopenharmony_ci{
8278c2ecf20Sopenharmony_ci	struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev);
8288c2ecf20Sopenharmony_ci
8298c2ecf20Sopenharmony_ci	return sprintf(buf, "%lu.%lu\n",
8308c2ecf20Sopenharmony_ci		FIELD_GET(PROTO_REV_MAJOR_MASK, scpi_info->protocol_version),
8318c2ecf20Sopenharmony_ci		FIELD_GET(PROTO_REV_MINOR_MASK, scpi_info->protocol_version));
8328c2ecf20Sopenharmony_ci}
8338c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(protocol_version);
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_cistatic ssize_t firmware_version_show(struct device *dev,
8368c2ecf20Sopenharmony_ci				     struct device_attribute *attr, char *buf)
8378c2ecf20Sopenharmony_ci{
8388c2ecf20Sopenharmony_ci	struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev);
8398c2ecf20Sopenharmony_ci
8408c2ecf20Sopenharmony_ci	return sprintf(buf, "%lu.%lu.%lu\n",
8418c2ecf20Sopenharmony_ci		FIELD_GET(FW_REV_MAJOR_MASK, scpi_info->firmware_version),
8428c2ecf20Sopenharmony_ci		FIELD_GET(FW_REV_MINOR_MASK, scpi_info->firmware_version),
8438c2ecf20Sopenharmony_ci		FIELD_GET(FW_REV_PATCH_MASK, scpi_info->firmware_version));
8448c2ecf20Sopenharmony_ci}
8458c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(firmware_version);
8468c2ecf20Sopenharmony_ci
8478c2ecf20Sopenharmony_cistatic struct attribute *versions_attrs[] = {
8488c2ecf20Sopenharmony_ci	&dev_attr_firmware_version.attr,
8498c2ecf20Sopenharmony_ci	&dev_attr_protocol_version.attr,
8508c2ecf20Sopenharmony_ci	NULL,
8518c2ecf20Sopenharmony_ci};
8528c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(versions);
8538c2ecf20Sopenharmony_ci
8548c2ecf20Sopenharmony_cistatic void scpi_free_channels(void *data)
8558c2ecf20Sopenharmony_ci{
8568c2ecf20Sopenharmony_ci	struct scpi_drvinfo *info = data;
8578c2ecf20Sopenharmony_ci	int i;
8588c2ecf20Sopenharmony_ci
8598c2ecf20Sopenharmony_ci	for (i = 0; i < info->num_chans; i++)
8608c2ecf20Sopenharmony_ci		mbox_free_channel(info->channels[i].chan);
8618c2ecf20Sopenharmony_ci}
8628c2ecf20Sopenharmony_ci
8638c2ecf20Sopenharmony_cistatic int scpi_remove(struct platform_device *pdev)
8648c2ecf20Sopenharmony_ci{
8658c2ecf20Sopenharmony_ci	int i;
8668c2ecf20Sopenharmony_ci	struct scpi_drvinfo *info = platform_get_drvdata(pdev);
8678c2ecf20Sopenharmony_ci
8688c2ecf20Sopenharmony_ci	scpi_info = NULL; /* stop exporting SCPI ops through get_scpi_ops */
8698c2ecf20Sopenharmony_ci
8708c2ecf20Sopenharmony_ci	for (i = 0; i < MAX_DVFS_DOMAINS && info->dvfs[i]; i++) {
8718c2ecf20Sopenharmony_ci		kfree(info->dvfs[i]->opps);
8728c2ecf20Sopenharmony_ci		kfree(info->dvfs[i]);
8738c2ecf20Sopenharmony_ci	}
8748c2ecf20Sopenharmony_ci
8758c2ecf20Sopenharmony_ci	return 0;
8768c2ecf20Sopenharmony_ci}
8778c2ecf20Sopenharmony_ci
8788c2ecf20Sopenharmony_ci#define MAX_SCPI_XFERS		10
8798c2ecf20Sopenharmony_cistatic int scpi_alloc_xfer_list(struct device *dev, struct scpi_chan *ch)
8808c2ecf20Sopenharmony_ci{
8818c2ecf20Sopenharmony_ci	int i;
8828c2ecf20Sopenharmony_ci	struct scpi_xfer *xfers;
8838c2ecf20Sopenharmony_ci
8848c2ecf20Sopenharmony_ci	xfers = devm_kcalloc(dev, MAX_SCPI_XFERS, sizeof(*xfers), GFP_KERNEL);
8858c2ecf20Sopenharmony_ci	if (!xfers)
8868c2ecf20Sopenharmony_ci		return -ENOMEM;
8878c2ecf20Sopenharmony_ci
8888c2ecf20Sopenharmony_ci	ch->xfers = xfers;
8898c2ecf20Sopenharmony_ci	for (i = 0; i < MAX_SCPI_XFERS; i++, xfers++) {
8908c2ecf20Sopenharmony_ci		init_completion(&xfers->done);
8918c2ecf20Sopenharmony_ci		list_add_tail(&xfers->node, &ch->xfers_list);
8928c2ecf20Sopenharmony_ci	}
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_ci	return 0;
8958c2ecf20Sopenharmony_ci}
8968c2ecf20Sopenharmony_ci
8978c2ecf20Sopenharmony_cistatic const struct of_device_id legacy_scpi_of_match[] = {
8988c2ecf20Sopenharmony_ci	{.compatible = "arm,scpi-pre-1.0"},
8998c2ecf20Sopenharmony_ci	{},
9008c2ecf20Sopenharmony_ci};
9018c2ecf20Sopenharmony_ci
9028c2ecf20Sopenharmony_cistatic int scpi_probe(struct platform_device *pdev)
9038c2ecf20Sopenharmony_ci{
9048c2ecf20Sopenharmony_ci	int count, idx, ret;
9058c2ecf20Sopenharmony_ci	struct resource res;
9068c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
9078c2ecf20Sopenharmony_ci	struct device_node *np = dev->of_node;
9088c2ecf20Sopenharmony_ci	struct scpi_drvinfo *scpi_drvinfo;
9098c2ecf20Sopenharmony_ci
9108c2ecf20Sopenharmony_ci	scpi_drvinfo = devm_kzalloc(dev, sizeof(*scpi_drvinfo), GFP_KERNEL);
9118c2ecf20Sopenharmony_ci	if (!scpi_drvinfo)
9128c2ecf20Sopenharmony_ci		return -ENOMEM;
9138c2ecf20Sopenharmony_ci
9148c2ecf20Sopenharmony_ci	if (of_match_device(legacy_scpi_of_match, &pdev->dev))
9158c2ecf20Sopenharmony_ci		scpi_drvinfo->is_legacy = true;
9168c2ecf20Sopenharmony_ci
9178c2ecf20Sopenharmony_ci	count = of_count_phandle_with_args(np, "mboxes", "#mbox-cells");
9188c2ecf20Sopenharmony_ci	if (count < 0) {
9198c2ecf20Sopenharmony_ci		dev_err(dev, "no mboxes property in '%pOF'\n", np);
9208c2ecf20Sopenharmony_ci		return -ENODEV;
9218c2ecf20Sopenharmony_ci	}
9228c2ecf20Sopenharmony_ci
9238c2ecf20Sopenharmony_ci	scpi_drvinfo->channels =
9248c2ecf20Sopenharmony_ci		devm_kcalloc(dev, count, sizeof(struct scpi_chan), GFP_KERNEL);
9258c2ecf20Sopenharmony_ci	if (!scpi_drvinfo->channels)
9268c2ecf20Sopenharmony_ci		return -ENOMEM;
9278c2ecf20Sopenharmony_ci
9288c2ecf20Sopenharmony_ci	ret = devm_add_action(dev, scpi_free_channels, scpi_drvinfo);
9298c2ecf20Sopenharmony_ci	if (ret)
9308c2ecf20Sopenharmony_ci		return ret;
9318c2ecf20Sopenharmony_ci
9328c2ecf20Sopenharmony_ci	for (; scpi_drvinfo->num_chans < count; scpi_drvinfo->num_chans++) {
9338c2ecf20Sopenharmony_ci		resource_size_t size;
9348c2ecf20Sopenharmony_ci		int idx = scpi_drvinfo->num_chans;
9358c2ecf20Sopenharmony_ci		struct scpi_chan *pchan = scpi_drvinfo->channels + idx;
9368c2ecf20Sopenharmony_ci		struct mbox_client *cl = &pchan->cl;
9378c2ecf20Sopenharmony_ci		struct device_node *shmem = of_parse_phandle(np, "shmem", idx);
9388c2ecf20Sopenharmony_ci
9398c2ecf20Sopenharmony_ci		ret = of_address_to_resource(shmem, 0, &res);
9408c2ecf20Sopenharmony_ci		of_node_put(shmem);
9418c2ecf20Sopenharmony_ci		if (ret) {
9428c2ecf20Sopenharmony_ci			dev_err(dev, "failed to get SCPI payload mem resource\n");
9438c2ecf20Sopenharmony_ci			return ret;
9448c2ecf20Sopenharmony_ci		}
9458c2ecf20Sopenharmony_ci
9468c2ecf20Sopenharmony_ci		size = resource_size(&res);
9478c2ecf20Sopenharmony_ci		pchan->rx_payload = devm_ioremap(dev, res.start, size);
9488c2ecf20Sopenharmony_ci		if (!pchan->rx_payload) {
9498c2ecf20Sopenharmony_ci			dev_err(dev, "failed to ioremap SCPI payload\n");
9508c2ecf20Sopenharmony_ci			return -EADDRNOTAVAIL;
9518c2ecf20Sopenharmony_ci		}
9528c2ecf20Sopenharmony_ci		pchan->tx_payload = pchan->rx_payload + (size >> 1);
9538c2ecf20Sopenharmony_ci
9548c2ecf20Sopenharmony_ci		cl->dev = dev;
9558c2ecf20Sopenharmony_ci		cl->rx_callback = scpi_handle_remote_msg;
9568c2ecf20Sopenharmony_ci		cl->tx_prepare = scpi_tx_prepare;
9578c2ecf20Sopenharmony_ci		cl->tx_block = true;
9588c2ecf20Sopenharmony_ci		cl->tx_tout = 20;
9598c2ecf20Sopenharmony_ci		cl->knows_txdone = false; /* controller can't ack */
9608c2ecf20Sopenharmony_ci
9618c2ecf20Sopenharmony_ci		INIT_LIST_HEAD(&pchan->rx_pending);
9628c2ecf20Sopenharmony_ci		INIT_LIST_HEAD(&pchan->xfers_list);
9638c2ecf20Sopenharmony_ci		spin_lock_init(&pchan->rx_lock);
9648c2ecf20Sopenharmony_ci		mutex_init(&pchan->xfers_lock);
9658c2ecf20Sopenharmony_ci
9668c2ecf20Sopenharmony_ci		ret = scpi_alloc_xfer_list(dev, pchan);
9678c2ecf20Sopenharmony_ci		if (!ret) {
9688c2ecf20Sopenharmony_ci			pchan->chan = mbox_request_channel(cl, idx);
9698c2ecf20Sopenharmony_ci			if (!IS_ERR(pchan->chan))
9708c2ecf20Sopenharmony_ci				continue;
9718c2ecf20Sopenharmony_ci			ret = PTR_ERR(pchan->chan);
9728c2ecf20Sopenharmony_ci			if (ret != -EPROBE_DEFER)
9738c2ecf20Sopenharmony_ci				dev_err(dev, "failed to get channel%d err %d\n",
9748c2ecf20Sopenharmony_ci					idx, ret);
9758c2ecf20Sopenharmony_ci		}
9768c2ecf20Sopenharmony_ci		return ret;
9778c2ecf20Sopenharmony_ci	}
9788c2ecf20Sopenharmony_ci
9798c2ecf20Sopenharmony_ci	scpi_drvinfo->commands = scpi_std_commands;
9808c2ecf20Sopenharmony_ci
9818c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, scpi_drvinfo);
9828c2ecf20Sopenharmony_ci
9838c2ecf20Sopenharmony_ci	if (scpi_drvinfo->is_legacy) {
9848c2ecf20Sopenharmony_ci		/* Replace with legacy variants */
9858c2ecf20Sopenharmony_ci		scpi_ops.clk_set_val = legacy_scpi_clk_set_val;
9868c2ecf20Sopenharmony_ci		scpi_drvinfo->commands = scpi_legacy_commands;
9878c2ecf20Sopenharmony_ci
9888c2ecf20Sopenharmony_ci		/* Fill priority bitmap */
9898c2ecf20Sopenharmony_ci		for (idx = 0; idx < ARRAY_SIZE(legacy_hpriority_cmds); idx++)
9908c2ecf20Sopenharmony_ci			set_bit(legacy_hpriority_cmds[idx],
9918c2ecf20Sopenharmony_ci				scpi_drvinfo->cmd_priority);
9928c2ecf20Sopenharmony_ci	}
9938c2ecf20Sopenharmony_ci
9948c2ecf20Sopenharmony_ci	scpi_info = scpi_drvinfo;
9958c2ecf20Sopenharmony_ci
9968c2ecf20Sopenharmony_ci	ret = scpi_init_versions(scpi_drvinfo);
9978c2ecf20Sopenharmony_ci	if (ret) {
9988c2ecf20Sopenharmony_ci		dev_err(dev, "incorrect or no SCP firmware found\n");
9998c2ecf20Sopenharmony_ci		scpi_info = NULL;
10008c2ecf20Sopenharmony_ci		return ret;
10018c2ecf20Sopenharmony_ci	}
10028c2ecf20Sopenharmony_ci
10038c2ecf20Sopenharmony_ci	if (scpi_drvinfo->is_legacy && !scpi_drvinfo->protocol_version &&
10048c2ecf20Sopenharmony_ci	    !scpi_drvinfo->firmware_version)
10058c2ecf20Sopenharmony_ci		dev_info(dev, "SCP Protocol legacy pre-1.0 firmware\n");
10068c2ecf20Sopenharmony_ci	else
10078c2ecf20Sopenharmony_ci		dev_info(dev, "SCP Protocol %lu.%lu Firmware %lu.%lu.%lu version\n",
10088c2ecf20Sopenharmony_ci			 FIELD_GET(PROTO_REV_MAJOR_MASK,
10098c2ecf20Sopenharmony_ci				   scpi_drvinfo->protocol_version),
10108c2ecf20Sopenharmony_ci			 FIELD_GET(PROTO_REV_MINOR_MASK,
10118c2ecf20Sopenharmony_ci				   scpi_drvinfo->protocol_version),
10128c2ecf20Sopenharmony_ci			 FIELD_GET(FW_REV_MAJOR_MASK,
10138c2ecf20Sopenharmony_ci				   scpi_drvinfo->firmware_version),
10148c2ecf20Sopenharmony_ci			 FIELD_GET(FW_REV_MINOR_MASK,
10158c2ecf20Sopenharmony_ci				   scpi_drvinfo->firmware_version),
10168c2ecf20Sopenharmony_ci			 FIELD_GET(FW_REV_PATCH_MASK,
10178c2ecf20Sopenharmony_ci				   scpi_drvinfo->firmware_version));
10188c2ecf20Sopenharmony_ci
10198c2ecf20Sopenharmony_ci	scpi_drvinfo->scpi_ops = &scpi_ops;
10208c2ecf20Sopenharmony_ci
10218c2ecf20Sopenharmony_ci	ret = devm_of_platform_populate(dev);
10228c2ecf20Sopenharmony_ci	if (ret)
10238c2ecf20Sopenharmony_ci		scpi_info = NULL;
10248c2ecf20Sopenharmony_ci
10258c2ecf20Sopenharmony_ci	return ret;
10268c2ecf20Sopenharmony_ci}
10278c2ecf20Sopenharmony_ci
10288c2ecf20Sopenharmony_cistatic const struct of_device_id scpi_of_match[] = {
10298c2ecf20Sopenharmony_ci	{.compatible = "arm,scpi"},
10308c2ecf20Sopenharmony_ci	{.compatible = "arm,scpi-pre-1.0"},
10318c2ecf20Sopenharmony_ci	{},
10328c2ecf20Sopenharmony_ci};
10338c2ecf20Sopenharmony_ci
10348c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, scpi_of_match);
10358c2ecf20Sopenharmony_ci
10368c2ecf20Sopenharmony_cistatic struct platform_driver scpi_driver = {
10378c2ecf20Sopenharmony_ci	.driver = {
10388c2ecf20Sopenharmony_ci		.name = "scpi_protocol",
10398c2ecf20Sopenharmony_ci		.of_match_table = scpi_of_match,
10408c2ecf20Sopenharmony_ci		.dev_groups = versions_groups,
10418c2ecf20Sopenharmony_ci	},
10428c2ecf20Sopenharmony_ci	.probe = scpi_probe,
10438c2ecf20Sopenharmony_ci	.remove = scpi_remove,
10448c2ecf20Sopenharmony_ci};
10458c2ecf20Sopenharmony_cimodule_platform_driver(scpi_driver);
10468c2ecf20Sopenharmony_ci
10478c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
10488c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ARM SCPI mailbox protocol driver");
10498c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
1050