diff options
Diffstat (limited to 'drivers/net/ipa/ipa_uc.c')
| -rw-r--r-- | drivers/net/ipa/ipa_uc.c | 167 |
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 */ |
