649 lines
16 KiB
C
649 lines
16 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (C) 2014-2020, Samsung Electronics.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_platform.h>
|
||
|
#include <linux/dma-mapping.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/bitops.h>
|
||
|
|
||
|
#include <soc/samsung/mcu_ipc.h>
|
||
|
#include "mcu_ipc_priv.h"
|
||
|
#include "modem_utils.h"
|
||
|
|
||
|
/* IRQ handler */
|
||
|
static irqreturn_t cp_mbox_irq_handler(int irq, void *data)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = NULL;
|
||
|
u32 irq_stat;
|
||
|
int i;
|
||
|
|
||
|
irq_data = (struct cp_mbox_irq_data *)data;
|
||
|
if (!irq_data) {
|
||
|
mif_err_limited("irq_data is null\n");
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err_limited("irq_data %d is disabled\n", irq_data->idx);
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Check raised interrupts
|
||
|
* Only clear and handle unmasked interrupts
|
||
|
*/
|
||
|
spin_lock(&mbox_data.reg_lock);
|
||
|
|
||
|
irq_stat = mcu_ipc_read(irq_data->sfr_rx.sr) & irq_data->sfr_rx.mask;
|
||
|
irq_stat &= ~(mcu_ipc_read(irq_data->sfr_rx.mr)) & irq_data->sfr_rx.mask;
|
||
|
mcu_ipc_write(irq_stat, irq_data->sfr_rx.cr);
|
||
|
|
||
|
spin_unlock(&mbox_data.reg_lock);
|
||
|
|
||
|
/* Call handlers */
|
||
|
for (i = 0; i < MAX_CP_MBOX_HANDLER; i++) {
|
||
|
if (irq_stat & (1 << (i + irq_data->sfr_rx.shift))) {
|
||
|
if ((1 << (i + irq_data->sfr_rx.shift)) & irq_data->registered_irq) {
|
||
|
irq_data->hd[i].handler(i, irq_data->hd[i].data);
|
||
|
} else {
|
||
|
mif_err_limited("unregistered:%d %d 0x%08x 0x%08lx 0x%08x\n",
|
||
|
irq_data->idx, i, irq_stat,
|
||
|
irq_data->unmasked_irq << irq_data->sfr_rx.shift,
|
||
|
mcu_ipc_read(irq_data->sfr_rx.mr));
|
||
|
}
|
||
|
|
||
|
irq_stat &= ~(1 << (i + irq_data->sfr_rx.shift));
|
||
|
}
|
||
|
|
||
|
if (!irq_stat)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
/* Register / Unregister */
|
||
|
int cp_mbox_register_handler(u32 idx, u32 int_num, irq_handler_t handler, void *data)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = &mbox_data.irq_data[idx];
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (!handler) {
|
||
|
mif_err_limited("handler is null\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (int_num >= MAX_CP_MBOX_HANDLER) {
|
||
|
mif_err_limited("int_num error:%d\n", int_num);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err_limited("irq_data %d is disabled\n", irq_data->idx);
|
||
|
return -EACCES;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
irq_data->hd[int_num].data = data;
|
||
|
irq_data->hd[int_num].handler = handler;
|
||
|
irq_data->registered_irq |= 1 << (int_num + irq_data->sfr_rx.shift);
|
||
|
set_bit(int_num, &irq_data->unmasked_irq);
|
||
|
|
||
|
spin_unlock_irqrestore(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
cp_mbox_enable_handler(irq_data->idx, int_num);
|
||
|
mif_info("idx:%d num:%d intmr0:0x%08x\n",
|
||
|
irq_data->idx, int_num, mcu_ipc_read(irq_data->sfr_rx.mr));
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_register_handler);
|
||
|
|
||
|
int cp_mbox_unregister_handler(u32 idx, u32 int_num, irq_handler_t handler)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = &mbox_data.irq_data[idx];
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (!handler) {
|
||
|
mif_err_limited("handler is null\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (irq_data->hd[int_num].handler != handler) {
|
||
|
mif_err_limited("int_num error:%d\n", int_num);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err_limited("irq_data %d is disabled\n", irq_data->idx);
|
||
|
return -EACCES;
|
||
|
}
|
||
|
|
||
|
cp_mbox_disable_handler(irq_data->idx, int_num);
|
||
|
mif_info("idx:%d num:%d intmr0:0x%08x\n",
|
||
|
irq_data->idx, int_num, mcu_ipc_read(irq_data->sfr_rx.mr));
|
||
|
|
||
|
spin_lock_irqsave(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
irq_data->hd[int_num].data = NULL;
|
||
|
irq_data->hd[int_num].handler = NULL;
|
||
|
irq_data->registered_irq &= ~(1 << (int_num + irq_data->sfr_rx.shift));
|
||
|
clear_bit(int_num, &irq_data->unmasked_irq);
|
||
|
|
||
|
spin_unlock_irqrestore(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_unregister_handler);
|
||
|
|
||
|
/* Handler : Enable / Disable */
|
||
|
int cp_mbox_enable_handler(u32 idx, u32 int_num)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = &mbox_data.irq_data[idx];
|
||
|
unsigned long flags;
|
||
|
unsigned long tmp;
|
||
|
|
||
|
/* The irq should have been registered. */
|
||
|
if (!(irq_data->registered_irq & BIT(int_num + irq_data->sfr_rx.shift))) {
|
||
|
mif_err_limited("int_num is not registered 0x%x %d\n",
|
||
|
irq_data->registered_irq, int_num);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err_limited("irq_data %d is disabled\n", irq_data->idx);
|
||
|
return -EACCES;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
tmp = mcu_ipc_read(irq_data->sfr_rx.mr);
|
||
|
|
||
|
/* Clear the mask if it was set. */
|
||
|
if (test_and_clear_bit(int_num + irq_data->sfr_rx.shift, &tmp))
|
||
|
mcu_ipc_write(tmp, irq_data->sfr_rx.mr);
|
||
|
|
||
|
/* Mark the irq as unmasked */
|
||
|
set_bit(int_num, &irq_data->unmasked_irq);
|
||
|
|
||
|
spin_unlock_irqrestore(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_enable_handler);
|
||
|
|
||
|
int cp_mbox_disable_handler(u32 idx, u32 int_num)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = &mbox_data.irq_data[idx];
|
||
|
unsigned long flags;
|
||
|
unsigned long irq_mask;
|
||
|
|
||
|
/* The interrupt must have been registered. */
|
||
|
if (!(irq_data->registered_irq & BIT(int_num + irq_data->sfr_rx.shift))) {
|
||
|
mif_err_limited("int_num is not registered 0x%x %d\n",
|
||
|
irq_data->registered_irq, int_num);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err_limited("irq_data %d is disabled\n", irq_data->idx);
|
||
|
return -EACCES;
|
||
|
}
|
||
|
|
||
|
/* Set the mask */
|
||
|
spin_lock_irqsave(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
irq_mask = mcu_ipc_read(irq_data->sfr_rx.mr);
|
||
|
|
||
|
/* Set the mask if it was not already set */
|
||
|
if (!test_and_set_bit(int_num + irq_data->sfr_rx.shift, &irq_mask)) {
|
||
|
mcu_ipc_write(irq_mask, irq_data->sfr_rx.mr);
|
||
|
udelay(5);
|
||
|
|
||
|
/* Reset the status bit to signal interrupt needs handling */
|
||
|
mcu_ipc_write(BIT(int_num + irq_data->sfr_rx.shift), irq_data->sfr_rx.gr);
|
||
|
udelay(5);
|
||
|
}
|
||
|
|
||
|
/* Remove the irq from the umasked irqs */
|
||
|
clear_bit(int_num, &irq_data->unmasked_irq);
|
||
|
|
||
|
spin_unlock_irqrestore(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_disable_handler);
|
||
|
|
||
|
/*
|
||
|
* This function is used to check the state of the mailbox interrupt
|
||
|
* when the interrupt after the interrupt has been masked. This can be
|
||
|
* used to check if a new interrupt has been set after being masked. A
|
||
|
* masked interrupt will have its status set but will not generate a hard
|
||
|
* interrupt. This function will check and clear the status.
|
||
|
*/
|
||
|
int cp_mbox_check_handler(u32 idx, u32 int_num)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = &mbox_data.irq_data[idx];
|
||
|
unsigned long flags;
|
||
|
u32 irq_stat;
|
||
|
|
||
|
/* Interrupt must have been registered. */
|
||
|
if (!(irq_data->registered_irq & BIT(int_num + irq_data->sfr_rx.shift))) {
|
||
|
mif_err_limited("int_num is not registered 0x%x %d\n",
|
||
|
irq_data->registered_irq, int_num);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err_limited("irq_data %d is disabled\n", irq_data->idx);
|
||
|
return -EACCES;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
/* Interrupt must have been masked. */
|
||
|
if (test_bit(int_num, &irq_data->unmasked_irq)) {
|
||
|
spin_unlock_irqrestore(&mbox_data.reg_lock, flags);
|
||
|
mif_err_limited("Mailbox interrupt (idx: %d, num: %d) is unmasked!\n",
|
||
|
irq_data->idx, int_num);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Check and clear the interrupt status bit. */
|
||
|
irq_stat = mcu_ipc_read(irq_data->sfr_rx.sr) & BIT(int_num + irq_data->sfr_rx.shift);
|
||
|
if (irq_stat)
|
||
|
mcu_ipc_write(irq_stat, irq_data->sfr_rx.cr);
|
||
|
|
||
|
spin_unlock_irqrestore(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
return irq_stat != 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_check_handler);
|
||
|
|
||
|
/* Set AP2CP interrupt */
|
||
|
void cp_mbox_set_interrupt(u32 idx, u32 int_num)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = &mbox_data.irq_data[idx];
|
||
|
|
||
|
mcu_ipc_write((0x1 << int_num) << irq_data->sfr_tx.shift, irq_data->sfr_tx.gr);
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_set_interrupt);
|
||
|
|
||
|
/* Shared register : Get / Set / Extract / Update / Dump */
|
||
|
static bool is_valid_sr(u32 sr_num)
|
||
|
{
|
||
|
if (!mbox_data.num_shared_reg) {
|
||
|
mif_err("num_shared_reg is 0\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (sr_num > mbox_data.num_shared_reg) {
|
||
|
mif_err("num_shared_reg is %d:%d\n",
|
||
|
sr_num, mbox_data.num_shared_reg);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
u32 cp_mbox_get_sr(u32 sr_num)
|
||
|
{
|
||
|
if (!is_valid_sr(sr_num))
|
||
|
return 0;
|
||
|
|
||
|
return mcu_ipc_read(mbox_data.shared_reg_offset + (4 * sr_num));
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_get_sr);
|
||
|
|
||
|
u32 cp_mbox_extract_sr(u32 sr_num, u32 mask, u32 pos)
|
||
|
{
|
||
|
if (!is_valid_sr(sr_num))
|
||
|
return 0;
|
||
|
|
||
|
return (cp_mbox_get_sr(sr_num) >> pos) & mask;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_extract_sr);
|
||
|
|
||
|
void cp_mbox_set_sr(u32 sr_num, u32 msg)
|
||
|
{
|
||
|
if (!is_valid_sr(sr_num))
|
||
|
return;
|
||
|
|
||
|
mcu_ipc_write(msg, mbox_data.shared_reg_offset + (4 * sr_num));
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_set_sr);
|
||
|
|
||
|
void cp_mbox_update_sr(u32 sr_num, u32 msg, u32 mask, u32 pos)
|
||
|
{
|
||
|
u32 val;
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (!is_valid_sr(sr_num))
|
||
|
return;
|
||
|
|
||
|
spin_lock_irqsave(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
val = cp_mbox_get_sr(sr_num);
|
||
|
val &= ~(mask << pos);
|
||
|
val |= (msg & mask) << pos;
|
||
|
cp_mbox_set_sr(sr_num, val);
|
||
|
|
||
|
spin_unlock_irqrestore(&mbox_data.reg_lock, flags);
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_update_sr);
|
||
|
|
||
|
void cp_mbox_dump_sr(void)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
u32 i, value;
|
||
|
|
||
|
spin_lock_irqsave(&mbox_data.reg_lock, flags);
|
||
|
|
||
|
for (i = 0; i < mbox_data.num_shared_reg; i++) {
|
||
|
value = mcu_ipc_read(mbox_data.shared_reg_offset + (4 * i));
|
||
|
mif_info("mbox dump: 0x%02x: 0x%04x\n", i, value);
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&mbox_data.reg_lock, flags);
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_dump_sr);
|
||
|
|
||
|
/* Reset */
|
||
|
void cp_mbox_reset(void)
|
||
|
{
|
||
|
u32 reg_val;
|
||
|
int i;
|
||
|
|
||
|
mif_info("Reset mailbox registers\n");
|
||
|
|
||
|
if (mbox_data.use_sw_reset_reg) {
|
||
|
reg_val = mcu_ipc_read(EXYNOS_MCU_IPC_MCUCTLR);
|
||
|
reg_val |= (0x1 << MCU_IPC_MCUCTLR_MSWRST);
|
||
|
|
||
|
mcu_ipc_write(reg_val, EXYNOS_MCU_IPC_MCUCTLR);
|
||
|
udelay(5);
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < MAX_CP_MBOX_IRQ_IDX; i++) {
|
||
|
struct cp_mbox_irq_data *irq_data = NULL;
|
||
|
|
||
|
irq_data = &mbox_data.irq_data[i];
|
||
|
if (!irq_data || !irq_data->name)
|
||
|
break;
|
||
|
|
||
|
mcu_ipc_write(~(irq_data->unmasked_irq) << irq_data->sfr_rx.shift,
|
||
|
irq_data->sfr_rx.mr);
|
||
|
mif_info("idx:%d intmr0:0x%08x\n", irq_data->idx,
|
||
|
mcu_ipc_read(irq_data->sfr_rx.mr));
|
||
|
|
||
|
mcu_ipc_write(irq_data->sfr_rx.mask, irq_data->sfr_rx.cr);
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_reset);
|
||
|
|
||
|
/* IRQ affinity */
|
||
|
int cp_mbox_get_affinity(u32 idx)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = &mbox_data.irq_data[idx];
|
||
|
|
||
|
if (!irq_data) {
|
||
|
mif_err("irq_data %d is null\n", idx);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err_limited("irq_data %d is disabled\n", irq_data->idx);
|
||
|
return -EACCES;
|
||
|
}
|
||
|
|
||
|
return irq_data->affinity;
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_get_affinity);
|
||
|
|
||
|
int cp_mbox_set_affinity(u32 idx, int affinity)
|
||
|
{
|
||
|
struct cp_mbox_irq_data *irq_data = &mbox_data.irq_data[idx];
|
||
|
int num_cpu;
|
||
|
|
||
|
if (!irq_data) {
|
||
|
mif_err("irq_data %d is null\n", idx);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err_limited("irq_data %d is disabled\n", irq_data->idx);
|
||
|
return -EACCES;
|
||
|
}
|
||
|
|
||
|
#if defined(CONFIG_VENDOR_NR_CPUS)
|
||
|
num_cpu = CONFIG_VENDOR_NR_CPUS;
|
||
|
#else
|
||
|
num_cpu = 8;
|
||
|
#endif
|
||
|
if (affinity >= num_cpu) {
|
||
|
mif_err("idx:%d affinity:%d error. cpu max:%d\n",
|
||
|
idx, affinity, num_cpu);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
mif_debug("idx:%d affinity:0x%x\n", idx, affinity);
|
||
|
irq_data->affinity = affinity;
|
||
|
|
||
|
return irq_set_affinity_hint(irq_data->irq, cpumask_of(affinity));
|
||
|
}
|
||
|
EXPORT_SYMBOL(cp_mbox_set_affinity);
|
||
|
|
||
|
/* Probe */
|
||
|
static int cp_mbox_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct device *dev = &pdev->dev;
|
||
|
struct device_node *irq_np = NULL;
|
||
|
struct device_node *irq_child_np = NULL;
|
||
|
u32 count = 0;
|
||
|
int irq;
|
||
|
int err = 0;
|
||
|
u32 idx = 0;
|
||
|
u32 offset[4] = {};
|
||
|
|
||
|
mif_info("+++\n");
|
||
|
|
||
|
if (!dev->of_node) {
|
||
|
mif_err("dev->of_node is null\n");
|
||
|
err = -ENODEV;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
/* DMA mask */
|
||
|
if (!pdev->dev.dma_mask)
|
||
|
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
|
||
|
if (!pdev->dev.coherent_dma_mask)
|
||
|
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
|
||
|
|
||
|
/* Region */
|
||
|
mbox_data.ioaddr = devm_platform_ioremap_resource(pdev, 0);
|
||
|
if (IS_ERR(mbox_data.ioaddr)) {
|
||
|
mif_err("failed to request memory resource\n");
|
||
|
err = PTR_ERR(mbox_data.ioaddr);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
mbox_data.dev = &pdev->dev;
|
||
|
spin_lock_init(&mbox_data.reg_lock);
|
||
|
|
||
|
/* Shared register */
|
||
|
mif_dt_read_u32(dev->of_node, "num_shared_reg", mbox_data.num_shared_reg);
|
||
|
mif_dt_read_u32(dev->of_node, "shared_reg_offset", mbox_data.shared_reg_offset);
|
||
|
mif_info("num_shared_reg:%d shared_reg_offset:0x%x\n",
|
||
|
mbox_data.num_shared_reg, mbox_data.shared_reg_offset);
|
||
|
|
||
|
/* SW reset reg */
|
||
|
mif_dt_read_bool(dev->of_node, "use_sw_reset_reg", mbox_data.use_sw_reset_reg);
|
||
|
mif_info("use_sw_reset_reg:%d\n", mbox_data.use_sw_reset_reg);
|
||
|
|
||
|
/* Interrupt */
|
||
|
irq_np = of_get_child_by_name(dev->of_node, "cp_mailbox_irqs");
|
||
|
if (!irq_np) {
|
||
|
mif_err("of_get_child_by_name() error:irq_np\n");
|
||
|
err = -EINVAL;
|
||
|
goto fail;
|
||
|
}
|
||
|
for_each_child_of_node(irq_np, irq_child_np) {
|
||
|
struct cp_mbox_irq_data *irq_data = NULL;
|
||
|
|
||
|
if (count >= MAX_CP_MBOX_IRQ_IDX) {
|
||
|
mif_err("count is full:%d\n", count);
|
||
|
err = -ENOMEM;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
/* IRQ index */
|
||
|
mif_dt_read_u32(irq_child_np, "cp_irq,idx", idx);
|
||
|
irq_data = &mbox_data.irq_data[idx];
|
||
|
if (!irq_data) {
|
||
|
mif_err("irq_data %d is null\n", idx);
|
||
|
err = -EINVAL;
|
||
|
goto fail;
|
||
|
}
|
||
|
irq_data->idx = idx;
|
||
|
|
||
|
/* Enable */
|
||
|
mif_dt_read_bool(irq_child_np, "cp_irq,enable", irq_data->enable);
|
||
|
if (!irq_data->enable) {
|
||
|
mif_err("irq_data %d is disabled\n", idx);
|
||
|
count++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Name */
|
||
|
mif_dt_read_string(irq_child_np, "cp_irq,name", irq_data->name);
|
||
|
|
||
|
/* SFR */
|
||
|
of_property_read_u32_array(irq_child_np, "cp_irq,sfr", offset, 4);
|
||
|
irq_data->sfr_rx.gr = EXYNOS_MCU_IPC_INTGR0 + offset[0];
|
||
|
irq_data->sfr_rx.cr = EXYNOS_MCU_IPC_INTCR0 + offset[0];
|
||
|
irq_data->sfr_rx.mr = EXYNOS_MCU_IPC_INTMR0 + offset[0];
|
||
|
irq_data->sfr_rx.sr = EXYNOS_MCU_IPC_INTSR0 + offset[0];
|
||
|
irq_data->sfr_rx.msr = EXYNOS_MCU_IPC_INTMSR0 + offset[0];
|
||
|
irq_data->sfr_rx.shift = offset[1];
|
||
|
irq_data->sfr_rx.mask = 0xFFFF << offset[1];
|
||
|
|
||
|
irq_data->sfr_tx.gr = EXYNOS_MCU_IPC_INTGR0 + offset[2];
|
||
|
irq_data->sfr_tx.cr = EXYNOS_MCU_IPC_INTCR0 + offset[2];
|
||
|
irq_data->sfr_tx.mr = EXYNOS_MCU_IPC_INTMR0 + offset[2];
|
||
|
irq_data->sfr_tx.sr = EXYNOS_MCU_IPC_INTSR0 + offset[2];
|
||
|
irq_data->sfr_tx.msr = EXYNOS_MCU_IPC_INTMSR0 + offset[2];
|
||
|
irq_data->sfr_tx.shift = offset[3];
|
||
|
irq_data->sfr_tx.mask = 0xFFFF << offset[3];
|
||
|
|
||
|
/* Request IRQ */
|
||
|
irq = platform_get_irq(pdev, irq_data->idx);
|
||
|
err = devm_request_irq(&pdev->dev, irq, cp_mbox_irq_handler,
|
||
|
IRQF_ONESHOT, irq_data->name, irq_data);
|
||
|
if (err) {
|
||
|
mif_err("devm_request_irq() error:%d\n", err);
|
||
|
goto fail;
|
||
|
}
|
||
|
err = enable_irq_wake(irq);
|
||
|
if (err) {
|
||
|
mif_err("enable_irq_wake() error:%d\n", err);
|
||
|
goto fail;
|
||
|
}
|
||
|
irq_data->irq = irq;
|
||
|
|
||
|
/* IRQ affinity */
|
||
|
mif_dt_read_u32(irq_child_np, "cp_irq,affinity", irq_data->affinity);
|
||
|
err = cp_mbox_set_affinity(irq_data->idx, irq_data->affinity);
|
||
|
if (err)
|
||
|
mif_err("cp_mbox_set_affinity() error:%d\n", err);
|
||
|
|
||
|
/* Init CP2AP interrupt */
|
||
|
mcu_ipc_write(irq_data->sfr_rx.mask, irq_data->sfr_rx.mr);
|
||
|
mcu_ipc_write(irq_data->sfr_rx.mask, irq_data->sfr_rx.cr);
|
||
|
|
||
|
mif_info("count:%d idx:%d name:%s rx.gr:0x%02x rx.shift:%d tx.gr:0x%02x tx.shift:%d affinity:%d mr:0x%08x\n",
|
||
|
count, irq_data->idx, irq_data->name,
|
||
|
irq_data->sfr_rx.gr, irq_data->sfr_rx.shift,
|
||
|
irq_data->sfr_tx.gr, irq_data->sfr_tx.shift,
|
||
|
irq_data->affinity, mcu_ipc_read(irq_data->sfr_rx.mr));
|
||
|
|
||
|
count++;
|
||
|
}
|
||
|
|
||
|
dev_set_drvdata(dev, &mbox_data);
|
||
|
|
||
|
mif_info("---\n");
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
fail:
|
||
|
panic("CP mbox probe failed\n");
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int cp_mbox_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int cp_mbox_suspend(struct device *dev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int cp_mbox_resume(struct device *dev)
|
||
|
{
|
||
|
struct cp_mbox_drv_data *data = dev->driver_data;
|
||
|
int i;
|
||
|
|
||
|
if (!data) {
|
||
|
mif_err_limited("data is null\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < MAX_CP_MBOX_IRQ_IDX; i++) {
|
||
|
struct cp_mbox_irq_data *irq_data = NULL;
|
||
|
|
||
|
irq_data = &data->irq_data[i];
|
||
|
if (!irq_data) {
|
||
|
mif_err_limited("irq_data %d is null\n", i);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!irq_data->enable)
|
||
|
continue;
|
||
|
|
||
|
cp_mbox_set_affinity(irq_data->idx, irq_data->affinity);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct dev_pm_ops cp_mbox_pm_ops = {
|
||
|
.suspend = cp_mbox_suspend,
|
||
|
.resume = cp_mbox_resume,
|
||
|
};
|
||
|
|
||
|
static const struct of_device_id cp_mbox_dt_match[] = {
|
||
|
{ .compatible = "samsung,exynos-cp-mailbox", },
|
||
|
{},
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, cp_mbox_dt_match);
|
||
|
|
||
|
static struct platform_driver cp_mbox_driver = {
|
||
|
.probe = cp_mbox_probe,
|
||
|
.remove = cp_mbox_remove,
|
||
|
.driver = {
|
||
|
.name = "cp_mailbox",
|
||
|
.owner = THIS_MODULE,
|
||
|
.of_match_table = of_match_ptr(cp_mbox_dt_match),
|
||
|
.pm = &cp_mbox_pm_ops,
|
||
|
.suppress_bind_attrs = true,
|
||
|
},
|
||
|
};
|
||
|
module_platform_driver(cp_mbox_driver);
|
||
|
|
||
|
MODULE_DESCRIPTION("Exynos CP mailbox driver");
|
||
|
MODULE_LICENSE("GPL");
|