blob: be1e26596cffefa66acf5af16b139ff2f9bdd671 [file] [log] [blame]
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
* Copyright 2017-2018 NXP
*
* SPDX-License-Identifier: GPL-2.0+
*/
/* Includes */
#include <linux/arm-smccc.h>
#include <linux/completion.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_fdt.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/mx8_mu.h>
#include <linux/syscore_ops.h>
#include <linux/suspend.h>
#include <soc/imx/fsl_hvc.h>
#include <soc/imx8/sc/svc/irq/api.h>
#include <soc/imx8/sc/ipc.h>
#include <soc/imx8/sc/sci.h>
#include "rpc.h"
/* Local Defines */
#define MU_SIZE 0x10000
/* Local Types */
unsigned int scu_mu_id;
static void __iomem *mu_base_virtaddr;
static struct delayed_work scu_mu_work;
static sc_ipc_t mu_ipcHandle;
/* Local functions */
/* Local variables */
static uint32_t gIPCport;
static sc_rpc_msg_t *rx_msg;
static bool scu_mu_init;
struct completion rx_completion;
DEFINE_MUTEX(scu_mu_mutex);
static BLOCKING_NOTIFIER_HEAD(SCU_notifier_chain);
EXPORT_SYMBOL(sc_pm_set_resource_power_mode);
EXPORT_SYMBOL(sc_pm_get_resource_power_mode);
EXPORT_SYMBOL(sc_pm_cpu_start);
EXPORT_SYMBOL(sc_misc_set_control);
EXPORT_SYMBOL(sc_pm_clock_enable);
EXPORT_SYMBOL(sc_pm_set_clock_rate);
/*--------------------------------------------------------------------------*/
/* RPC command/response */
/*--------------------------------------------------------------------------*/
void sc_call_rpc(sc_ipc_t handle, sc_rpc_msg_t *msg, sc_bool_t no_resp)
{
struct arm_smccc_res res;
unsigned long timeout;
if (in_interrupt()) {
pr_warn("Cannot make SC IPC calls from an interrupt context\n");
dump_stack();
return;
}
mutex_lock(&scu_mu_mutex);
reinit_completion(&rx_completion);
rx_msg = msg;
if (xen_initial_domain()) {
arm_smccc_hvc(FSL_HVC_SC, (uint64_t)msg, no_resp, 0, 0, 0, 0,
0, &res);
if (res.a0)
printk("Error FSL_HVC_SC %ld\n", res.a0);
} else {
sc_ipc_write(handle, msg);
if (!no_resp) {
timeout = wait_for_completion_timeout(&rx_completion, HZ / 10);
if (!timeout) {
pr_err("Timeout for IPC response!\n");
mutex_unlock(&scu_mu_mutex);
return;
}
}
}
mutex_unlock(&scu_mu_mutex);
}
EXPORT_SYMBOL(sc_call_rpc);
/*--------------------------------------------------------------------------*/
/* Get MU base address for specified IPC channel */
/*--------------------------------------------------------------------------*/
static uint32_t *sc_ipc_get_mu_base(uint32_t id)
{
uint32_t *base;
/* Check parameters */
if (id >= SC_NUM_IPC)
base = NULL;
else
base = (uint32_t *) (mu_base_virtaddr + (id * MU_SIZE));
return base;
}
/*--------------------------------------------------------------------------*/
/* Get the MU ID used by Linux */
/*--------------------------------------------------------------------------*/
int sc_ipc_getMuID(uint32_t *mu_id)
{
if (scu_mu_init) {
*mu_id = scu_mu_id;
return SC_ERR_NONE;
}
return SC_ERR_UNAVAILABLE;
}
EXPORT_SYMBOL(sc_ipc_getMuID);
/*--------------------------------------------------------------------------*/
/* Open an IPC channel */
/*--------------------------------------------------------------------------*/
sc_err_t sc_ipc_requestInt(sc_ipc_t *handle, uint32_t id)
{
return SC_ERR_NONE;
}
/*--------------------------------------------------------------------------*/
/* Open an IPC channel */
/*--------------------------------------------------------------------------*/
sc_err_t sc_ipc_open(sc_ipc_t *handle, uint32_t id)
{
uint32_t *base;
mutex_lock(&scu_mu_mutex);
if (!scu_mu_init) {
mutex_unlock(&scu_mu_mutex);
return SC_ERR_UNAVAILABLE;
}
/* Get MU base associated with IPC channel */
base = sc_ipc_get_mu_base(id);
if (base == NULL) {
mutex_unlock(&scu_mu_mutex);
return SC_ERR_IPC;
}
*handle = (sc_ipc_t) task_pid_vnr(current);
mutex_unlock(&scu_mu_mutex);
return SC_ERR_NONE;
}
EXPORT_SYMBOL(sc_ipc_open);
/*--------------------------------------------------------------------------*/
/* Close an IPC channel */
/*--------------------------------------------------------------------------*/
void sc_ipc_close(sc_ipc_t handle)
{
uint32_t *base;
mutex_lock(&scu_mu_mutex);
if (!scu_mu_init) {
mutex_unlock(&scu_mu_mutex);
return;
}
/* Get MU base associated with IPC channel */
base = sc_ipc_get_mu_base(gIPCport);
/* TBD ***** What needs to be done here? */
mutex_unlock(&scu_mu_mutex);
}
EXPORT_SYMBOL(sc_ipc_close);
/*!
* This function reads a message from an IPC channel.
*
* @param[in] ipc id of channel read from
* @param[out] data pointer to message buffer to read
*
* This function will block if no message is available to be read.
*/
void sc_ipc_read(sc_ipc_t handle, void *data)
{
uint32_t *base;
uint8_t count = 0;
sc_rpc_msg_t *msg = (sc_rpc_msg_t *) data;
/* Get MU base associated with IPC channel */
base = sc_ipc_get_mu_base(gIPCport);
if ((base == NULL) || (msg == NULL))
return;
/* Read first word */
MU_ReceiveMsg(base, 0, (uint32_t *) msg);
count++;
/* Check size */
if (msg->size > SC_RPC_MAX_MSG) {
*((uint32_t *) msg) = 0;
return;
}
/* Read remaining words */
while (count < msg->size) {
MU_ReceiveMsg(base, count % MU_RR_COUNT,
&(msg->DATA.u32[count - 1]));
count++;
}
}
/*!
* This function writes a message to an IPC channel.
*
* @param[in] ipc id of channel to write to
* @param[in] data pointer to message buffer to write
*
* This function will block if the outgoing buffer is full.
*/
void sc_ipc_write(sc_ipc_t handle, const void *data)
{
uint32_t *base;
uint8_t count = 0;
sc_rpc_msg_t *msg = (sc_rpc_msg_t *) data;
/* Get MU base associated with IPC channel */
base = sc_ipc_get_mu_base(gIPCport);
if ((base == NULL) || (msg == NULL))
return;
/* Check size */
if (msg->size > SC_RPC_MAX_MSG)
return;
/* Write first word */
MU_SendMessage(base, 0, *((uint32_t *) msg));
count++;
/* Write remaining words */
while (count < msg->size) {
MU_SendMessage(base, count % MU_TR_COUNT, msg->DATA.u32[count - 1]);
count++;
}
}
int register_scu_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&SCU_notifier_chain, nb);
}
EXPORT_SYMBOL(register_scu_notifier);
int unregister_scu_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&SCU_notifier_chain, nb);
}
EXPORT_SYMBOL(unregister_scu_notifier);
static int SCU_notifier_call_chain(unsigned long status, sc_irq_group_t *group)
{
return blocking_notifier_call_chain(&SCU_notifier_chain, status,
(void *)group);
}
static void scu_mu_work_handler(struct work_struct *work)
{
uint32_t irq_status;
sc_err_t sciErr;
sc_irq_group_t i;
/* Walk all groups interrupt callback, callback will judge if it's
* the right group for itself, return directly if not.
*/
for (i = 0; i < SC_IRQ_NUM_GROUP; i++) {
sciErr = sc_irq_status(mu_ipcHandle, SC_R_MU_1A, i,
&irq_status);
/* no irq? */
if (!irq_status)
continue;
SCU_notifier_call_chain(irq_status, &i);
}
}
static irqreturn_t imx8_scu_mu_isr(int irq, void *param)
{
u32 irqs;
irqs = (readl_relaxed(mu_base_virtaddr + 0x20) & (0xf << 24));
if (irqs) {
sc_ipc_read(mu_ipcHandle, rx_msg);
complete(&rx_completion);
}
irqs = (readl_relaxed(mu_base_virtaddr + 0x20) & (0xf << 28));
if (irqs) {
/* Clear the General Interrupt */
writel_relaxed(irqs, mu_base_virtaddr + 0x20);
/* Setup a bottom-half to handle the irq work. */
schedule_delayed_work(&scu_mu_work, 0);
pm_system_wakeup();
}
return IRQ_HANDLED;
}
static void imx8_mu_resume(void)
{
int i;
MU_Init(mu_base_virtaddr);
MU_EnableRxFullInt(mu_base_virtaddr, 0);
for (i = 0; i < MU_RR_COUNT; i++)
MU_EnableGeneralInt(mu_base_virtaddr, i);
}
struct syscore_ops imx8_mu_syscore_ops = {
.resume = imx8_mu_resume,
};
/*Initialization of the MU code. */
int __init imx8_mu_init(void)
{
struct device_node *np;
int irq;
int err;
sc_err_t sciErr;
/*
* Get the address of MU to be used for communication with the SCU
*/
np = of_find_compatible_node(NULL, NULL, "fsl,imx8-mu");
if (!np) {
pr_info("Cannot find MU entry in device tree\n");
return 0;
}
mu_base_virtaddr = of_iomap(np, 0);
WARN_ON(!mu_base_virtaddr);
err = of_property_read_u32_index(np, "fsl,scu_ap_mu_id", 0, &scu_mu_id);
if (err)
pr_info("imx8_mu_init: Cannot get mu_id err = %d\n", err);
irq = of_irq_get(np, 0);
if (irq <= 0) {
/* SCU works just fine without irq */
pr_warn("imx8_mu_init: no irq: %d\n", irq);
} else {
err = request_irq(irq, imx8_scu_mu_isr,
IRQF_NO_SUSPEND, "imx8_mu_isr", NULL);
if (err) {
pr_err("imx8_mu_init: request_irq %d failed: %d\n",
irq, err);
return err;
}
irq_set_irq_wake(irq, 1);
}
if (!scu_mu_init) {
uint32_t i;
INIT_DELAYED_WORK(&scu_mu_work, scu_mu_work_handler);
/* Init MU */
MU_Init(mu_base_virtaddr);
MU_EnableRxFullInt(mu_base_virtaddr, 0);
#if 1
/* Enable all RX interrupts */
for (i = 0; i < MU_RR_COUNT; i++)
MU_EnableGeneralInt(mu_base_virtaddr, i);
#endif
gIPCport = scu_mu_id;
scu_mu_init = true;
init_completion(&rx_completion);
}
sciErr = sc_ipc_open(&mu_ipcHandle, scu_mu_id);
if (sciErr != SC_ERR_NONE) {
pr_info("Cannot open MU channel to SCU\n");
return sciErr;
};
/* Request for the high temp interrupt. */
sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_TEMP,
SC_IRQ_TEMP_PMIC0_HIGH, true);
if (sciErr)
pr_info("Cannot request PMIC0_TEMP interrupt\n");
/* Request for the high temp interrupt. */
sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_TEMP,
SC_IRQ_TEMP_PMIC1_HIGH, true);
if (sciErr)
pr_info("Cannot request PMIC1_TEMP interrupt\n");
/* Request for the rtc alarm interrupt. */
sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_RTC,
SC_IRQ_RTC, true);
if (sciErr)
pr_info("Cannot request ALARM_RTC interrupt\n");
/* Request for the ON/OFF interrupt. */
sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_WAKE,
SC_IRQ_BUTTON, true);
if (sciErr)
pr_info("Cannot request ON/OFF interrupt\n");
/* Request for the watchdog interrupt. */
sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_WDOG,
SC_IRQ_WDOG, true);
if (sciErr)
pr_info("Cannot request WDOG interrupt\n");
sciErr = sc_irq_enable(mu_ipcHandle, SC_R_MU_1A, SC_IRQ_GROUP_WAKE,
SC_IRQ_PAD, true);
if (sciErr)
pr_info("Cannot request PAD interrupt\n");
register_syscore_ops(&imx8_mu_syscore_ops);
pr_info("*****Initialized MU\n");
return scu_mu_id;
}
early_initcall(imx8_mu_init);