162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2013-2016 Freescale Semiconductor Inc.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * I/O services to send MC commands to the MC hardware
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/ioport.h>
1262306a36Sopenharmony_ci#include <linux/device.h>
1362306a36Sopenharmony_ci#include <linux/io.h>
1462306a36Sopenharmony_ci#include <linux/io-64-nonatomic-hi-lo.h>
1562306a36Sopenharmony_ci#include <linux/fsl/mc.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "fsl-mc-private.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/*
2062306a36Sopenharmony_ci * Timeout in milliseconds to wait for the completion of an MC command
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ci#define MC_CMD_COMPLETION_TIMEOUT_MS	500
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/*
2562306a36Sopenharmony_ci * usleep_range() min and max values used to throttle down polling
2662306a36Sopenharmony_ci * iterations while waiting for MC command completion
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_ci#define MC_CMD_COMPLETION_POLLING_MIN_SLEEP_USECS    10
2962306a36Sopenharmony_ci#define MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS    500
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic enum mc_cmd_status mc_cmd_hdr_read_status(struct fsl_mc_command *cmd)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	struct mc_cmd_header *hdr = (struct mc_cmd_header *)&cmd->header;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	return (enum mc_cmd_status)hdr->status;
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ciu16 mc_cmd_hdr_read_cmdid(struct fsl_mc_command *cmd)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	struct mc_cmd_header *hdr = (struct mc_cmd_header *)&cmd->header;
4162306a36Sopenharmony_ci	u16 cmd_id = le16_to_cpu(hdr->cmd_id);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	return cmd_id;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic int mc_status_to_error(enum mc_cmd_status status)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	static const int mc_status_to_error_map[] = {
4962306a36Sopenharmony_ci		[MC_CMD_STATUS_OK] = 0,
5062306a36Sopenharmony_ci		[MC_CMD_STATUS_AUTH_ERR] = -EACCES,
5162306a36Sopenharmony_ci		[MC_CMD_STATUS_NO_PRIVILEGE] = -EPERM,
5262306a36Sopenharmony_ci		[MC_CMD_STATUS_DMA_ERR] = -EIO,
5362306a36Sopenharmony_ci		[MC_CMD_STATUS_CONFIG_ERR] = -ENXIO,
5462306a36Sopenharmony_ci		[MC_CMD_STATUS_TIMEOUT] = -ETIMEDOUT,
5562306a36Sopenharmony_ci		[MC_CMD_STATUS_NO_RESOURCE] = -ENAVAIL,
5662306a36Sopenharmony_ci		[MC_CMD_STATUS_NO_MEMORY] = -ENOMEM,
5762306a36Sopenharmony_ci		[MC_CMD_STATUS_BUSY] = -EBUSY,
5862306a36Sopenharmony_ci		[MC_CMD_STATUS_UNSUPPORTED_OP] = -ENOTSUPP,
5962306a36Sopenharmony_ci		[MC_CMD_STATUS_INVALID_STATE] = -ENODEV,
6062306a36Sopenharmony_ci	};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if ((u32)status >= ARRAY_SIZE(mc_status_to_error_map))
6362306a36Sopenharmony_ci		return -EINVAL;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	return mc_status_to_error_map[status];
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic const char *mc_status_to_string(enum mc_cmd_status status)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	static const char *const status_strings[] = {
7162306a36Sopenharmony_ci		[MC_CMD_STATUS_OK] = "Command completed successfully",
7262306a36Sopenharmony_ci		[MC_CMD_STATUS_READY] = "Command ready to be processed",
7362306a36Sopenharmony_ci		[MC_CMD_STATUS_AUTH_ERR] = "Authentication error",
7462306a36Sopenharmony_ci		[MC_CMD_STATUS_NO_PRIVILEGE] = "No privilege",
7562306a36Sopenharmony_ci		[MC_CMD_STATUS_DMA_ERR] = "DMA or I/O error",
7662306a36Sopenharmony_ci		[MC_CMD_STATUS_CONFIG_ERR] = "Configuration error",
7762306a36Sopenharmony_ci		[MC_CMD_STATUS_TIMEOUT] = "Operation timed out",
7862306a36Sopenharmony_ci		[MC_CMD_STATUS_NO_RESOURCE] = "No resources",
7962306a36Sopenharmony_ci		[MC_CMD_STATUS_NO_MEMORY] = "No memory available",
8062306a36Sopenharmony_ci		[MC_CMD_STATUS_BUSY] = "Device is busy",
8162306a36Sopenharmony_ci		[MC_CMD_STATUS_UNSUPPORTED_OP] = "Unsupported operation",
8262306a36Sopenharmony_ci		[MC_CMD_STATUS_INVALID_STATE] = "Invalid state"
8362306a36Sopenharmony_ci	};
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	if ((unsigned int)status >= ARRAY_SIZE(status_strings))
8662306a36Sopenharmony_ci		return "Unknown MC error";
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return status_strings[status];
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/**
9262306a36Sopenharmony_ci * mc_write_command - writes a command to a Management Complex (MC) portal
9362306a36Sopenharmony_ci *
9462306a36Sopenharmony_ci * @portal: pointer to an MC portal
9562306a36Sopenharmony_ci * @cmd: pointer to a filled command
9662306a36Sopenharmony_ci */
9762306a36Sopenharmony_cistatic inline void mc_write_command(struct fsl_mc_command __iomem *portal,
9862306a36Sopenharmony_ci				    struct fsl_mc_command *cmd)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	int i;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/* copy command parameters into the portal */
10362306a36Sopenharmony_ci	for (i = 0; i < MC_CMD_NUM_OF_PARAMS; i++)
10462306a36Sopenharmony_ci		/*
10562306a36Sopenharmony_ci		 * Data is already in the expected LE byte-order. Do an
10662306a36Sopenharmony_ci		 * extra LE -> CPU conversion so that the CPU -> LE done in
10762306a36Sopenharmony_ci		 * the device io write api puts it back in the right order.
10862306a36Sopenharmony_ci		 */
10962306a36Sopenharmony_ci		writeq_relaxed(le64_to_cpu(cmd->params[i]), &portal->params[i]);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	/* submit the command by writing the header */
11262306a36Sopenharmony_ci	writeq(le64_to_cpu(cmd->header), &portal->header);
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/**
11662306a36Sopenharmony_ci * mc_read_response - reads the response for the last MC command from a
11762306a36Sopenharmony_ci * Management Complex (MC) portal
11862306a36Sopenharmony_ci *
11962306a36Sopenharmony_ci * @portal: pointer to an MC portal
12062306a36Sopenharmony_ci * @resp: pointer to command response buffer
12162306a36Sopenharmony_ci *
12262306a36Sopenharmony_ci * Returns MC_CMD_STATUS_OK on Success; Error code otherwise.
12362306a36Sopenharmony_ci */
12462306a36Sopenharmony_cistatic inline enum mc_cmd_status mc_read_response(struct fsl_mc_command __iomem
12562306a36Sopenharmony_ci						  *portal,
12662306a36Sopenharmony_ci						  struct fsl_mc_command *resp)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	int i;
12962306a36Sopenharmony_ci	enum mc_cmd_status status;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	/* Copy command response header from MC portal: */
13262306a36Sopenharmony_ci	resp->header = cpu_to_le64(readq_relaxed(&portal->header));
13362306a36Sopenharmony_ci	status = mc_cmd_hdr_read_status(resp);
13462306a36Sopenharmony_ci	if (status != MC_CMD_STATUS_OK)
13562306a36Sopenharmony_ci		return status;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* Copy command response data from MC portal: */
13862306a36Sopenharmony_ci	for (i = 0; i < MC_CMD_NUM_OF_PARAMS; i++)
13962306a36Sopenharmony_ci		/*
14062306a36Sopenharmony_ci		 * Data is expected to be in LE byte-order. Do an
14162306a36Sopenharmony_ci		 * extra CPU -> LE to revert the LE -> CPU done in
14262306a36Sopenharmony_ci		 * the device io read api.
14362306a36Sopenharmony_ci		 */
14462306a36Sopenharmony_ci		resp->params[i] =
14562306a36Sopenharmony_ci			cpu_to_le64(readq_relaxed(&portal->params[i]));
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	return status;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci/**
15162306a36Sopenharmony_ci * mc_polling_wait_preemptible() - Waits for the completion of an MC
15262306a36Sopenharmony_ci *                                 command doing preemptible polling.
15362306a36Sopenharmony_ci *                                 uslepp_range() is called between
15462306a36Sopenharmony_ci *                                 polling iterations.
15562306a36Sopenharmony_ci * @mc_io: MC I/O object to be used
15662306a36Sopenharmony_ci * @cmd: command buffer to receive MC response
15762306a36Sopenharmony_ci * @mc_status: MC command completion status
15862306a36Sopenharmony_ci */
15962306a36Sopenharmony_cistatic int mc_polling_wait_preemptible(struct fsl_mc_io *mc_io,
16062306a36Sopenharmony_ci				       struct fsl_mc_command *cmd,
16162306a36Sopenharmony_ci				       enum mc_cmd_status *mc_status)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	enum mc_cmd_status status;
16462306a36Sopenharmony_ci	unsigned long jiffies_until_timeout =
16562306a36Sopenharmony_ci		jiffies + msecs_to_jiffies(MC_CMD_COMPLETION_TIMEOUT_MS);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	/*
16862306a36Sopenharmony_ci	 * Wait for response from the MC hardware:
16962306a36Sopenharmony_ci	 */
17062306a36Sopenharmony_ci	for (;;) {
17162306a36Sopenharmony_ci		status = mc_read_response(mc_io->portal_virt_addr, cmd);
17262306a36Sopenharmony_ci		if (status != MC_CMD_STATUS_READY)
17362306a36Sopenharmony_ci			break;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci		/*
17662306a36Sopenharmony_ci		 * TODO: When MC command completion interrupts are supported
17762306a36Sopenharmony_ci		 * call wait function here instead of usleep_range()
17862306a36Sopenharmony_ci		 */
17962306a36Sopenharmony_ci		usleep_range(MC_CMD_COMPLETION_POLLING_MIN_SLEEP_USECS,
18062306a36Sopenharmony_ci			     MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci		if (time_after_eq(jiffies, jiffies_until_timeout)) {
18362306a36Sopenharmony_ci			dev_dbg(mc_io->dev,
18462306a36Sopenharmony_ci				"MC command timed out (portal: %pa, dprc handle: %#x, command: %#x)\n",
18562306a36Sopenharmony_ci				 &mc_io->portal_phys_addr,
18662306a36Sopenharmony_ci				 (unsigned int)mc_cmd_hdr_read_token(cmd),
18762306a36Sopenharmony_ci				 (unsigned int)mc_cmd_hdr_read_cmdid(cmd));
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci			return -ETIMEDOUT;
19062306a36Sopenharmony_ci		}
19162306a36Sopenharmony_ci	}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	*mc_status = status;
19462306a36Sopenharmony_ci	return 0;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci/**
19862306a36Sopenharmony_ci * mc_polling_wait_atomic() - Waits for the completion of an MC command
19962306a36Sopenharmony_ci *                            doing atomic polling. udelay() is called
20062306a36Sopenharmony_ci *                            between polling iterations.
20162306a36Sopenharmony_ci * @mc_io: MC I/O object to be used
20262306a36Sopenharmony_ci * @cmd: command buffer to receive MC response
20362306a36Sopenharmony_ci * @mc_status: MC command completion status
20462306a36Sopenharmony_ci */
20562306a36Sopenharmony_cistatic int mc_polling_wait_atomic(struct fsl_mc_io *mc_io,
20662306a36Sopenharmony_ci				  struct fsl_mc_command *cmd,
20762306a36Sopenharmony_ci				  enum mc_cmd_status *mc_status)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	enum mc_cmd_status status;
21062306a36Sopenharmony_ci	unsigned long timeout_usecs = MC_CMD_COMPLETION_TIMEOUT_MS * 1000;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	BUILD_BUG_ON((MC_CMD_COMPLETION_TIMEOUT_MS * 1000) %
21362306a36Sopenharmony_ci		     MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS != 0);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	for (;;) {
21662306a36Sopenharmony_ci		status = mc_read_response(mc_io->portal_virt_addr, cmd);
21762306a36Sopenharmony_ci		if (status != MC_CMD_STATUS_READY)
21862306a36Sopenharmony_ci			break;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci		udelay(MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS);
22162306a36Sopenharmony_ci		timeout_usecs -= MC_CMD_COMPLETION_POLLING_MAX_SLEEP_USECS;
22262306a36Sopenharmony_ci		if (timeout_usecs == 0) {
22362306a36Sopenharmony_ci			dev_dbg(mc_io->dev,
22462306a36Sopenharmony_ci				"MC command timed out (portal: %pa, dprc handle: %#x, command: %#x)\n",
22562306a36Sopenharmony_ci				 &mc_io->portal_phys_addr,
22662306a36Sopenharmony_ci				 (unsigned int)mc_cmd_hdr_read_token(cmd),
22762306a36Sopenharmony_ci				 (unsigned int)mc_cmd_hdr_read_cmdid(cmd));
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci			return -ETIMEDOUT;
23062306a36Sopenharmony_ci		}
23162306a36Sopenharmony_ci	}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	*mc_status = status;
23462306a36Sopenharmony_ci	return 0;
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci/**
23862306a36Sopenharmony_ci * mc_send_command() - Sends a command to the MC device using the given
23962306a36Sopenharmony_ci *                     MC I/O object
24062306a36Sopenharmony_ci * @mc_io: MC I/O object to be used
24162306a36Sopenharmony_ci * @cmd: command to be sent
24262306a36Sopenharmony_ci *
24362306a36Sopenharmony_ci * Returns '0' on Success; Error code otherwise.
24462306a36Sopenharmony_ci */
24562306a36Sopenharmony_ciint mc_send_command(struct fsl_mc_io *mc_io, struct fsl_mc_command *cmd)
24662306a36Sopenharmony_ci{
24762306a36Sopenharmony_ci	int error;
24862306a36Sopenharmony_ci	enum mc_cmd_status status;
24962306a36Sopenharmony_ci	unsigned long irq_flags = 0;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	if (in_irq() && !(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL))
25262306a36Sopenharmony_ci		return -EINVAL;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	if (mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)
25562306a36Sopenharmony_ci		raw_spin_lock_irqsave(&mc_io->spinlock, irq_flags);
25662306a36Sopenharmony_ci	else
25762306a36Sopenharmony_ci		mutex_lock(&mc_io->mutex);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	/*
26062306a36Sopenharmony_ci	 * Send command to the MC hardware:
26162306a36Sopenharmony_ci	 */
26262306a36Sopenharmony_ci	mc_write_command(mc_io->portal_virt_addr, cmd);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	/*
26562306a36Sopenharmony_ci	 * Wait for response from the MC hardware:
26662306a36Sopenharmony_ci	 */
26762306a36Sopenharmony_ci	if (!(mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL))
26862306a36Sopenharmony_ci		error = mc_polling_wait_preemptible(mc_io, cmd, &status);
26962306a36Sopenharmony_ci	else
27062306a36Sopenharmony_ci		error = mc_polling_wait_atomic(mc_io, cmd, &status);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	if (error < 0)
27362306a36Sopenharmony_ci		goto common_exit;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	if (status != MC_CMD_STATUS_OK) {
27662306a36Sopenharmony_ci		dev_dbg(mc_io->dev,
27762306a36Sopenharmony_ci			"MC command failed: portal: %pa, dprc handle: %#x, command: %#x, status: %s (%#x)\n",
27862306a36Sopenharmony_ci			 &mc_io->portal_phys_addr,
27962306a36Sopenharmony_ci			 (unsigned int)mc_cmd_hdr_read_token(cmd),
28062306a36Sopenharmony_ci			 (unsigned int)mc_cmd_hdr_read_cmdid(cmd),
28162306a36Sopenharmony_ci			 mc_status_to_string(status),
28262306a36Sopenharmony_ci			 (unsigned int)status);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci		error = mc_status_to_error(status);
28562306a36Sopenharmony_ci		goto common_exit;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	error = 0;
28962306a36Sopenharmony_cicommon_exit:
29062306a36Sopenharmony_ci	if (mc_io->flags & FSL_MC_IO_ATOMIC_CONTEXT_PORTAL)
29162306a36Sopenharmony_ci		raw_spin_unlock_irqrestore(&mc_io->spinlock, irq_flags);
29262306a36Sopenharmony_ci	else
29362306a36Sopenharmony_ci		mutex_unlock(&mc_io->mutex);
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	return error;
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mc_send_command);
298