summaryrefslogtreecommitdiff
path: root/drivers/net/ipa/ipa_uc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ipa/ipa_uc.c')
-rw-r--r--drivers/net/ipa/ipa_uc.c167
1 files changed, 108 insertions, 59 deletions
diff --git a/drivers/net/ipa/ipa_uc.c b/drivers/net/ipa/ipa_uc.c
index a1f8db00d55a..dc7e92f2a4fb 100644
--- a/drivers/net/ipa/ipa_uc.c
+++ b/drivers/net/ipa/ipa_uc.c
@@ -1,15 +1,18 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved.
- * Copyright (C) 2018-2020 Linaro Ltd.
+ * Copyright (C) 2018-2024 Linaro Ltd.
*/
-#include <linux/types.h>
-#include <linux/io.h>
#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/types.h>
#include "ipa.h"
-#include "ipa_clock.h"
+#include "ipa_interrupt.h"
+#include "ipa_power.h"
+#include "ipa_reg.h"
#include "ipa_uc.h"
/**
@@ -35,31 +38,34 @@
*/
/* Supports hardware interface version 0x2000 */
-/* Offset relative to the base of the IPA shared address space of the
- * shared region used for communication with the microcontroller. The
- * region is 128 bytes in size, but only the first 40 bytes are used.
- */
-#define IPA_MEM_UC_OFFSET 0x0000
-
/* Delay to allow a the microcontroller to save state when crashing */
#define IPA_SEND_DELAY 100 /* microseconds */
/**
* struct ipa_uc_mem_area - AP/microcontroller shared memory area
* @command: command code (AP->microcontroller)
+ * @reserved0: reserved bytes; avoid reading or writing
* @command_param: low 32 bits of command parameter (AP->microcontroller)
* @command_param_hi: high 32 bits of command parameter (AP->microcontroller)
*
* @response: response code (microcontroller->AP)
+ * @reserved1: reserved bytes; avoid reading or writing
* @response_param: response parameter (microcontroller->AP)
*
* @event: event code (microcontroller->AP)
+ * @reserved2: reserved bytes; avoid reading or writing
* @event_param: event parameter (microcontroller->AP)
*
* @first_error_address: address of first error-source on SNOC
* @hw_state: state of hardware (including error type information)
* @warning_counter: counter of non-fatal hardware errors
+ * @reserved3: reserved bytes; avoid reading or writing
* @interface_version: hardware-reported interface version
+ * @reserved4: reserved bytes; avoid reading or writing
+ *
+ * A shared memory area at the base of IPA resident memory is used for
+ * communication with the microcontroller. The region is 128 bytes in
+ * size, but only the first 40 bytes (structured this way) are used.
*/
struct ipa_uc_mem_area {
u8 command; /* enum ipa_uc_command */
@@ -83,119 +89,162 @@ struct ipa_uc_mem_area {
/** enum ipa_uc_command - commands from the AP to the microcontroller */
enum ipa_uc_command {
- IPA_UC_COMMAND_NO_OP = 0,
- IPA_UC_COMMAND_UPDATE_FLAGS = 1,
- IPA_UC_COMMAND_DEBUG_RUN_TEST = 2,
- IPA_UC_COMMAND_DEBUG_GET_INFO = 3,
- IPA_UC_COMMAND_ERR_FATAL = 4,
- IPA_UC_COMMAND_CLK_GATE = 5,
- IPA_UC_COMMAND_CLK_UNGATE = 6,
- IPA_UC_COMMAND_MEMCPY = 7,
- IPA_UC_COMMAND_RESET_PIPE = 8,
- IPA_UC_COMMAND_REG_WRITE = 9,
- IPA_UC_COMMAND_GSI_CH_EMPTY = 10,
+ IPA_UC_COMMAND_NO_OP = 0x0,
+ IPA_UC_COMMAND_UPDATE_FLAGS = 0x1,
+ IPA_UC_COMMAND_DEBUG_RUN_TEST = 0x2,
+ IPA_UC_COMMAND_DEBUG_GET_INFO = 0x3,
+ IPA_UC_COMMAND_ERR_FATAL = 0x4,
+ IPA_UC_COMMAND_CLK_GATE = 0x5,
+ IPA_UC_COMMAND_CLK_UNGATE = 0x6,
+ IPA_UC_COMMAND_MEMCPY = 0x7,
+ IPA_UC_COMMAND_RESET_PIPE = 0x8,
+ IPA_UC_COMMAND_REG_WRITE = 0x9,
+ IPA_UC_COMMAND_GSI_CH_EMPTY = 0xa,
};
/** enum ipa_uc_response - microcontroller response codes */
enum ipa_uc_response {
- IPA_UC_RESPONSE_NO_OP = 0,
- IPA_UC_RESPONSE_INIT_COMPLETED = 1,
- IPA_UC_RESPONSE_CMD_COMPLETED = 2,
- IPA_UC_RESPONSE_DEBUG_GET_INFO = 3,
+ IPA_UC_RESPONSE_NO_OP = 0x0,
+ IPA_UC_RESPONSE_INIT_COMPLETED = 0x1,
+ IPA_UC_RESPONSE_CMD_COMPLETED = 0x2,
+ IPA_UC_RESPONSE_DEBUG_GET_INFO = 0x3,
};
/** enum ipa_uc_event - common cpu events reported by the microcontroller */
enum ipa_uc_event {
- IPA_UC_EVENT_NO_OP = 0,
- IPA_UC_EVENT_ERROR = 1,
- IPA_UC_EVENT_LOG_INFO = 2,
+ IPA_UC_EVENT_NO_OP = 0x0,
+ IPA_UC_EVENT_ERROR = 0x1,
+ IPA_UC_EVENT_LOG_INFO = 0x2,
};
static struct ipa_uc_mem_area *ipa_uc_shared(struct ipa *ipa)
{
- u32 offset = ipa->mem_offset + ipa->mem[IPA_MEM_UC_SHARED].offset;
+ const struct ipa_mem *mem = ipa_mem_find(ipa, IPA_MEM_UC_SHARED);
+ u32 offset = ipa->mem_offset + mem->offset;
return ipa->mem_virt + offset;
}
/* Microcontroller event IPA interrupt handler */
-static void ipa_uc_event_handler(struct ipa *ipa, enum ipa_irq_id irq_id)
+static void ipa_uc_event_handler(struct ipa *ipa)
{
struct ipa_uc_mem_area *shared = ipa_uc_shared(ipa);
- struct device *dev = &ipa->pdev->dev;
+ struct device *dev = ipa->dev;
if (shared->event == IPA_UC_EVENT_ERROR)
dev_err(dev, "microcontroller error event\n");
- else
- dev_err(dev, "unsupported microcontroller event %hhu\n",
+ else if (shared->event != IPA_UC_EVENT_LOG_INFO)
+ dev_err(dev, "unsupported microcontroller event %u\n",
shared->event);
+ /* The LOG_INFO event can be safely ignored */
}
/* Microcontroller response IPA interrupt handler */
-static void ipa_uc_response_hdlr(struct ipa *ipa, enum ipa_irq_id irq_id)
+static void ipa_uc_response_hdlr(struct ipa *ipa)
{
struct ipa_uc_mem_area *shared = ipa_uc_shared(ipa);
+ struct device *dev = ipa->dev;
/* An INIT_COMPLETED response message is sent to the AP by the
* microcontroller when it is operational. Other than this, the AP
* should only receive responses from the microcontroller when it has
* sent it a request message.
*
- * We can drop the clock reference taken in ipa_uc_init() once we
+ * We can drop the power reference taken in ipa_uc_power() once we
* know the microcontroller has finished its initialization.
*/
switch (shared->response) {
case IPA_UC_RESPONSE_INIT_COMPLETED:
- ipa->uc_loaded = true;
- ipa_clock_put(ipa);
+ if (ipa->uc_powered) {
+ ipa->uc_loaded = true;
+ ipa_power_retention(ipa, true);
+ (void)pm_runtime_put_autosuspend(dev);
+ ipa->uc_powered = false;
+ } else {
+ dev_warn(dev, "unexpected init_completed response\n");
+ }
break;
default:
- dev_warn(&ipa->pdev->dev,
- "unsupported microcontroller response %hhu\n",
+ dev_warn(dev, "unsupported microcontroller response %u\n",
shared->response);
break;
}
}
-/* ipa_uc_setup() - Set up the microcontroller */
-void ipa_uc_setup(struct ipa *ipa)
+void ipa_uc_interrupt_handler(struct ipa *ipa, enum ipa_irq_id irq_id)
{
- /* The microcontroller needs the IPA clock running until it has
- * completed its initialization. It signals this by sending an
- * INIT_COMPLETED response message to the AP. This could occur after
- * we have finished doing the rest of the IPA initialization, so we
- * need to take an extra "proxy" reference, and hold it until we've
- * received that signal. (This reference is dropped in
- * ipa_uc_response_hdlr(), above.)
- */
- ipa_clock_get(ipa);
+ /* Silently ignore anything unrecognized */
+ if (irq_id == IPA_IRQ_UC_0)
+ ipa_uc_event_handler(ipa);
+ else if (irq_id == IPA_IRQ_UC_1)
+ ipa_uc_response_hdlr(ipa);
+}
+/* Configure the IPA microcontroller subsystem */
+void ipa_uc_config(struct ipa *ipa)
+{
+ ipa->uc_powered = false;
ipa->uc_loaded = false;
- ipa_interrupt_add(ipa->interrupt, IPA_IRQ_UC_0, ipa_uc_event_handler);
- ipa_interrupt_add(ipa->interrupt, IPA_IRQ_UC_1, ipa_uc_response_hdlr);
+ ipa_interrupt_enable(ipa, IPA_IRQ_UC_0);
+ ipa_interrupt_enable(ipa, IPA_IRQ_UC_1);
}
-/* Inverse of ipa_uc_setup() */
-void ipa_uc_teardown(struct ipa *ipa)
+/* Inverse of ipa_uc_config() */
+void ipa_uc_deconfig(struct ipa *ipa)
{
- ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_UC_1);
- ipa_interrupt_remove(ipa->interrupt, IPA_IRQ_UC_0);
- if (!ipa->uc_loaded)
- ipa_clock_put(ipa);
+ struct device *dev = ipa->dev;
+
+ ipa_interrupt_disable(ipa, IPA_IRQ_UC_1);
+ ipa_interrupt_disable(ipa, IPA_IRQ_UC_0);
+ if (ipa->uc_loaded)
+ ipa_power_retention(ipa, false);
+
+ if (!ipa->uc_powered)
+ return;
+
+ (void)pm_runtime_put_autosuspend(dev);
+}
+
+/* Take a proxy power reference for the microcontroller */
+void ipa_uc_power(struct ipa *ipa)
+{
+ struct device *dev = ipa->dev;
+ static bool already;
+ int ret;
+
+ if (already)
+ return;
+ already = true; /* Only do this on first boot */
+
+ /* This power reference dropped in ipa_uc_response_hdlr() above */
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ dev_err(dev, "error %d getting proxy power\n", ret);
+ } else {
+ ipa->uc_powered = true;
+ }
}
/* Send a command to the microcontroller */
static void send_uc_command(struct ipa *ipa, u32 command, u32 command_param)
{
struct ipa_uc_mem_area *shared = ipa_uc_shared(ipa);
+ const struct reg *reg;
+ u32 val;
+ /* Fill in the command data */
shared->command = command;
shared->command_param = cpu_to_le32(command_param);
shared->command_param_hi = 0;
shared->response = 0;
shared->response_param = 0;
- iowrite32(1, ipa->reg_virt + IPA_REG_IRQ_UC_OFFSET);
+ /* Use an interrupt to tell the microcontroller the command is ready */
+ reg = ipa_reg(ipa, IPA_IRQ_UC);
+ val = reg_bit(reg, UC_INTR);
+
+ iowrite32(val, ipa->reg_virt + reg_offset(reg));
}
/* Tell the microcontroller the AP is shutting down */