18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright 2013-2016 Freescale Semiconductor Inc.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * I/O services to send MC commands to the MC hardware
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/delay.h>
108c2ecf20Sopenharmony_ci#include <linux/slab.h>
118c2ecf20Sopenharmony_ci#include <linux/ioport.h>
128c2ecf20Sopenharmony_ci#include <linux/device.h>
138c2ecf20Sopenharmony_ci#include <linux/io.h>
148c2ecf20Sopenharmony_ci#include <linux/io-64-nonatomic-hi-lo.h>
158c2ecf20Sopenharmony_ci#include <linux/fsl/mc.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include "fsl-mc-private.h"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci/**
208c2ecf20Sopenharmony_ci * Timeout in milliseconds to wait for the completion of an MC command
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ci#define MC_CMD_COMPLETION_TIMEOUT_MS	500
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/*
258c2ecf20Sopenharmony_ci * usleep_range() min and max values used to throttle down polling
268c2ecf20Sopenharmony_ci * iterations while waiting for MC command completion
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci#define MC_CMD_COMPLETION_POLLING_MIN_SLEEP_USECS    10
298c2ecf20Sopenharmony_ci#define MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS    500
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic enum mc_cmd_status mc_cmd_hdr_read_status(struct fsl_mc_command *cmd)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	struct mc_cmd_header *hdr = (struct mc_cmd_header *)&cmd->header;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	return (enum mc_cmd_status)hdr->status;
368c2ecf20Sopenharmony_ci}
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic u16 mc_cmd_hdr_read_cmdid(struct fsl_mc_command *cmd)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct mc_cmd_header *hdr = (struct mc_cmd_header *)&cmd->header;
418c2ecf20Sopenharmony_ci	u16 cmd_id = le16_to_cpu(hdr->cmd_id);
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	return cmd_id;
448c2ecf20Sopenharmony_ci}
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic int mc_status_to_error(enum mc_cmd_status status)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	static const int mc_status_to_error_map[] = {
498c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_OK] = 0,
508c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_AUTH_ERR] = -EACCES,
518c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_NO_PRIVILEGE] = -EPERM,
528c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_DMA_ERR] = -EIO,
538c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_CONFIG_ERR] = -ENXIO,
548c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_TIMEOUT] = -ETIMEDOUT,
558c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_NO_RESOURCE] = -ENAVAIL,
568c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_NO_MEMORY] = -ENOMEM,
578c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_BUSY] = -EBUSY,
588c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_UNSUPPORTED_OP] = -ENOTSUPP,
598c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_INVALID_STATE] = -ENODEV,
608c2ecf20Sopenharmony_ci	};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	if ((u32)status >= ARRAY_SIZE(mc_status_to_error_map))
638c2ecf20Sopenharmony_ci		return -EINVAL;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	return mc_status_to_error_map[status];
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic const char *mc_status_to_string(enum mc_cmd_status status)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	static const char *const status_strings[] = {
718c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_OK] = "Command completed successfully",
728c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_READY] = "Command ready to be processed",
738c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_AUTH_ERR] = "Authentication error",
748c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_NO_PRIVILEGE] = "No privilege",
758c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_DMA_ERR] = "DMA or I/O error",
768c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_CONFIG_ERR] = "Configuration error",
778c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_TIMEOUT] = "Operation timed out",
788c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_NO_RESOURCE] = "No resources",
798c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_NO_MEMORY] = "No memory available",
808c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_BUSY] = "Device is busy",
818c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_UNSUPPORTED_OP] = "Unsupported operation",
828c2ecf20Sopenharmony_ci		[MC_CMD_STATUS_INVALID_STATE] = "Invalid state"
838c2ecf20Sopenharmony_ci	};
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	if ((unsigned int)status >= ARRAY_SIZE(status_strings))
868c2ecf20Sopenharmony_ci		return "Unknown MC error";
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	return status_strings[status];
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci/**
928c2ecf20Sopenharmony_ci * mc_write_command - writes a command to a Management Complex (MC) portal
938c2ecf20Sopenharmony_ci *
948c2ecf20Sopenharmony_ci * @portal: pointer to an MC portal
958c2ecf20Sopenharmony_ci * @cmd: pointer to a filled command
968c2ecf20Sopenharmony_ci */
978c2ecf20Sopenharmony_cistatic inline void mc_write_command(struct fsl_mc_command __iomem *portal,
988c2ecf20Sopenharmony_ci				    struct fsl_mc_command *cmd)
998c2ecf20Sopenharmony_ci{
1008c2ecf20Sopenharmony_ci	int i;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	/* copy command parameters into the portal */
1038c2ecf20Sopenharmony_ci	for (i = 0; i < MC_CMD_NUM_OF_PARAMS; i++)
1048c2ecf20Sopenharmony_ci		/*
1058c2ecf20Sopenharmony_ci		 * Data is already in the expected LE byte-order. Do an
1068c2ecf20Sopenharmony_ci		 * extra LE -> CPU conversion so that the CPU -> LE done in
1078c2ecf20Sopenharmony_ci		 * the device io write api puts it back in the right order.
1088c2ecf20Sopenharmony_ci		 */
1098c2ecf20Sopenharmony_ci		writeq_relaxed(le64_to_cpu(cmd->params[i]), &portal->params[i]);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	/* submit the command by writing the header */
1128c2ecf20Sopenharmony_ci	writeq(le64_to_cpu(cmd->header), &portal->header);
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci/**
1168c2ecf20Sopenharmony_ci * mc_read_response - reads the response for the last MC command from a
1178c2ecf20Sopenharmony_ci * Management Complex (MC) portal
1188c2ecf20Sopenharmony_ci *
1198c2ecf20Sopenharmony_ci * @portal: pointer to an MC portal
1208c2ecf20Sopenharmony_ci * @resp: pointer to command response buffer
1218c2ecf20Sopenharmony_ci *
1228c2ecf20Sopenharmony_ci * Returns MC_CMD_STATUS_OK on Success; Error code otherwise.
1238c2ecf20Sopenharmony_ci */
1248c2ecf20Sopenharmony_cistatic inline enum mc_cmd_status mc_read_response(struct fsl_mc_command __iomem
1258c2ecf20Sopenharmony_ci						  *portal,
1268c2ecf20Sopenharmony_ci						  struct fsl_mc_command *resp)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	int i;
1298c2ecf20Sopenharmony_ci	enum mc_cmd_status status;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/* Copy command response header from MC portal: */
1328c2ecf20Sopenharmony_ci	resp->header = cpu_to_le64(readq_relaxed(&portal->header));
1338c2ecf20Sopenharmony_ci	status = mc_cmd_hdr_read_status(resp);
1348c2ecf20Sopenharmony_ci	if (status != MC_CMD_STATUS_OK)
1358c2ecf20Sopenharmony_ci		return status;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/* Copy command response data from MC portal: */
1388c2ecf20Sopenharmony_ci	for (i = 0; i < MC_CMD_NUM_OF_PARAMS; i++)
1398c2ecf20Sopenharmony_ci		/*
1408c2ecf20Sopenharmony_ci		 * Data is expected to be in LE byte-order. Do an
1418c2ecf20Sopenharmony_ci		 * extra CPU -> LE to revert the LE -> CPU done in
1428c2ecf20Sopenharmony_ci		 * the device io read api.
1438c2ecf20Sopenharmony_ci		 */
1448c2ecf20Sopenharmony_ci		resp->params[i] =
1458c2ecf20Sopenharmony_ci			cpu_to_le64(readq_relaxed(&portal->params[i]));
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	return status;
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci/**
1518c2ecf20Sopenharmony_ci * Waits for the completion of an MC command doing preemptible polling.
1528c2ecf20Sopenharmony_ci * uslepp_range() is called between polling iterations.
1538c2ecf20Sopenharmony_ci *
1548c2ecf20Sopenharmony_ci * @mc_io: MC I/O object to be used
1558c2ecf20Sopenharmony_ci * @cmd: command buffer to receive MC response
1568c2ecf20Sopenharmony_ci * @mc_status: MC command completion status
1578c2ecf20Sopenharmony_ci */
1588c2ecf20Sopenharmony_cistatic int mc_polling_wait_preemptible(struct fsl_mc_io *mc_io,
1598c2ecf20Sopenharmony_ci				       struct fsl_mc_command *cmd,
1608c2ecf20Sopenharmony_ci				       enum mc_cmd_status *mc_status)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	enum mc_cmd_status status;
1638c2ecf20Sopenharmony_ci	unsigned long jiffies_until_timeout =
1648c2ecf20Sopenharmony_ci		jiffies + msecs_to_jiffies(MC_CMD_COMPLETION_TIMEOUT_MS);
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	/*
1678c2ecf20Sopenharmony_ci	 * Wait for response from the MC hardware:
1688c2ecf20Sopenharmony_ci	 */
1698c2ecf20Sopenharmony_ci	for (;;) {
1708c2ecf20Sopenharmony_ci		status = mc_read_response(mc_io->portal_virt_addr, cmd);
1718c2ecf20Sopenharmony_ci		if (status != MC_CMD_STATUS_READY)
1728c2ecf20Sopenharmony_ci			break;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		/*
1758c2ecf20Sopenharmony_ci		 * TODO: When MC command completion interrupts are supported
1768c2ecf20Sopenharmony_ci		 * call wait function here instead of usleep_range()
1778c2ecf20Sopenharmony_ci		 */
1788c2ecf20Sopenharmony_ci		usleep_range(MC_CMD_COMPLETION_POLLING_MIN_SLEEP_USECS,
1798c2ecf20Sopenharmony_ci			     MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci		if (time_after_eq(jiffies, jiffies_until_timeout)) {
1828c2ecf20Sopenharmony_ci			dev_dbg(mc_io->dev,
1838c2ecf20Sopenharmony_ci				"MC command timed out (portal: %pa, dprc handle: %#x, command: %#x)\n",
1848c2ecf20Sopenharmony_ci				 &mc_io->portal_phys_addr,
1858c2ecf20Sopenharmony_ci				 (unsigned int)mc_cmd_hdr_read_token(cmd),
1868c2ecf20Sopenharmony_ci				 (unsigned int)mc_cmd_hdr_read_cmdid(cmd));
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci			return -ETIMEDOUT;
1898c2ecf20Sopenharmony_ci		}
1908c2ecf20Sopenharmony_ci	}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	*mc_status = status;
1938c2ecf20Sopenharmony_ci	return 0;
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci/**
1978c2ecf20Sopenharmony_ci * Waits for the completion of an MC command doing atomic polling.
1988c2ecf20Sopenharmony_ci * udelay() is called between polling iterations.
1998c2ecf20Sopenharmony_ci *
2008c2ecf20Sopenharmony_ci * @mc_io: MC I/O object to be used
2018c2ecf20Sopenharmony_ci * @cmd: command buffer to receive MC response
2028c2ecf20Sopenharmony_ci * @mc_status: MC command completion status
2038c2ecf20Sopenharmony_ci */
2048c2ecf20Sopenharmony_cistatic int mc_polling_wait_atomic(struct fsl_mc_io *mc_io,
2058c2ecf20Sopenharmony_ci				  struct fsl_mc_command *cmd,
2068c2ecf20Sopenharmony_ci				  enum mc_cmd_status *mc_status)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	enum mc_cmd_status status;
2098c2ecf20Sopenharmony_ci	unsigned long timeout_usecs = MC_CMD_COMPLETION_TIMEOUT_MS * 1000;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	BUILD_BUG_ON((MC_CMD_COMPLETION_TIMEOUT_MS * 1000) %
2128c2ecf20Sopenharmony_ci		     MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS != 0);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	for (;;) {
2158c2ecf20Sopenharmony_ci		status = mc_read_response(mc_io->portal_virt_addr, cmd);
2168c2ecf20Sopenharmony_ci		if (status != MC_CMD_STATUS_READY)
2178c2ecf20Sopenharmony_ci			break;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci		udelay(MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS);
2208c2ecf20Sopenharmony_ci		timeout_usecs -= MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS;
2218c2ecf20Sopenharmony_ci		if (timeout_usecs == 0) {
2228c2ecf20Sopenharmony_ci			dev_dbg(mc_io->dev,
2238c2ecf20Sopenharmony_ci				"MC command timed out (portal: %pa, dprc handle: %#x, command: %#x)\n",
2248c2ecf20Sopenharmony_ci				 &mc_io->portal_phys_addr,
2258c2ecf20Sopenharmony_ci				 (unsigned int)mc_cmd_hdr_read_token(cmd),
2268c2ecf20Sopenharmony_ci				 (unsigned int)mc_cmd_hdr_read_cmdid(cmd));
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci			return -ETIMEDOUT;
2298c2ecf20Sopenharmony_ci		}
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	*mc_status = status;
2338c2ecf20Sopenharmony_ci	return 0;
2348c2ecf20Sopenharmony_ci}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci/**
2378c2ecf20Sopenharmony_ci * Sends a command to the MC device using the given MC I/O object
2388c2ecf20Sopenharmony_ci *
2398c2ecf20Sopenharmony_ci * @mc_io: MC I/O object to be used
2408c2ecf20Sopenharmony_ci * @cmd: command to be sent
2418c2ecf20Sopenharmony_ci *
2428c2ecf20Sopenharmony_ci * Returns '0' on Success; Error code otherwise.
2438c2ecf20Sopenharmony_ci */
2448c2ecf20Sopenharmony_ciint mc_send_command(struct fsl_mc_io *mc_io, struct fsl_mc_command *cmd)
2458c2ecf20Sopenharmony_ci{
2468c2ecf20Sopenharmony_ci	int error;
2478c2ecf20Sopenharmony_ci	enum mc_cmd_status status;
2488c2ecf20Sopenharmony_ci	unsigned long irq_flags = 0;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	if (in_irq() && !(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL))
2518c2ecf20Sopenharmony_ci		return -EINVAL;
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	if (mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)
2548c2ecf20Sopenharmony_ci		raw_spin_lock_irqsave(&mc_io->spinlock, irq_flags);
2558c2ecf20Sopenharmony_ci	else
2568c2ecf20Sopenharmony_ci		mutex_lock(&mc_io->mutex);
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	/*
2598c2ecf20Sopenharmony_ci	 * Send command to the MC hardware:
2608c2ecf20Sopenharmony_ci	 */
2618c2ecf20Sopenharmony_ci	mc_write_command(mc_io->portal_virt_addr, cmd);
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	/*
2648c2ecf20Sopenharmony_ci	 * Wait for response from the MC hardware:
2658c2ecf20Sopenharmony_ci	 */
2668c2ecf20Sopenharmony_ci	if (!(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL))
2678c2ecf20Sopenharmony_ci		error = mc_polling_wait_preemptible(mc_io, cmd, &status);
2688c2ecf20Sopenharmony_ci	else
2698c2ecf20Sopenharmony_ci		error = mc_polling_wait_atomic(mc_io, cmd, &status);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	if (error < 0)
2728c2ecf20Sopenharmony_ci		goto common_exit;
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	if (status != MC_CMD_STATUS_OK) {
2758c2ecf20Sopenharmony_ci		dev_dbg(mc_io->dev,
2768c2ecf20Sopenharmony_ci			"MC command failed: portal: %pa, dprc handle: %#x, command: %#x, status: %s (%#x)\n",
2778c2ecf20Sopenharmony_ci			 &mc_io->portal_phys_addr,
2788c2ecf20Sopenharmony_ci			 (unsigned int)mc_cmd_hdr_read_token(cmd),
2798c2ecf20Sopenharmony_ci			 (unsigned int)mc_cmd_hdr_read_cmdid(cmd),
2808c2ecf20Sopenharmony_ci			 mc_status_to_string(status),
2818c2ecf20Sopenharmony_ci			 (unsigned int)status);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci		error = mc_status_to_error(status);
2848c2ecf20Sopenharmony_ci		goto common_exit;
2858c2ecf20Sopenharmony_ci	}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci	error = 0;
2888c2ecf20Sopenharmony_cicommon_exit:
2898c2ecf20Sopenharmony_ci	if (mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)
2908c2ecf20Sopenharmony_ci		raw_spin_unlock_irqrestore(&mc_io->spinlock, irq_flags);
2918c2ecf20Sopenharmony_ci	else
2928c2ecf20Sopenharmony_ci		mutex_unlock(&mc_io->mutex);
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	return error;
2958c2ecf20Sopenharmony_ci}
2968c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(mc_send_command);
297