linux/drivers/mailbox/pl320-ipc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright 2012 Calxeda, Inc.
   4 */
   5#include <linux/types.h>
   6#include <linux/err.h>
   7#include <linux/delay.h>
   8#include <linux/export.h>
   9#include <linux/io.h>
  10#include <linux/interrupt.h>
  11#include <linux/completion.h>
  12#include <linux/mutex.h>
  13#include <linux/notifier.h>
  14#include <linux/spinlock.h>
  15#include <linux/device.h>
  16#include <linux/amba/bus.h>
  17
  18#include <linux/pl320-ipc.h>
  19
  20#define IPCMxSOURCE(m)          ((m) * 0x40)
  21#define IPCMxDSET(m)            (((m) * 0x40) + 0x004)
  22#define IPCMxDCLEAR(m)          (((m) * 0x40) + 0x008)
  23#define IPCMxDSTATUS(m)         (((m) * 0x40) + 0x00C)
  24#define IPCMxMODE(m)            (((m) * 0x40) + 0x010)
  25#define IPCMxMSET(m)            (((m) * 0x40) + 0x014)
  26#define IPCMxMCLEAR(m)          (((m) * 0x40) + 0x018)
  27#define IPCMxMSTATUS(m)         (((m) * 0x40) + 0x01C)
  28#define IPCMxSEND(m)            (((m) * 0x40) + 0x020)
  29#define IPCMxDR(m, dr)          (((m) * 0x40) + ((dr) * 4) + 0x024)
  30
  31#define IPCMMIS(irq)            (((irq) * 8) + 0x800)
  32#define IPCMRIS(irq)            (((irq) * 8) + 0x804)
  33
  34#define MBOX_MASK(n)            (1 << (n))
  35#define IPC_TX_MBOX             1
  36#define IPC_RX_MBOX             2
  37
  38#define CHAN_MASK(n)            (1 << (n))
  39#define A9_SOURCE               1
  40#define M3_SOURCE               0
  41
  42static void __iomem *ipc_base;
  43static int ipc_irq;
  44static DEFINE_MUTEX(ipc_m1_lock);
  45static DECLARE_COMPLETION(ipc_completion);
  46static ATOMIC_NOTIFIER_HEAD(ipc_notifier);
  47
  48static inline void set_destination(int source, int mbox)
  49{
  50        writel_relaxed(CHAN_MASK(source), ipc_base + IPCMxDSET(mbox));
  51        writel_relaxed(CHAN_MASK(source), ipc_base + IPCMxMSET(mbox));
  52}
  53
  54static inline void clear_destination(int source, int mbox)
  55{
  56        writel_relaxed(CHAN_MASK(source), ipc_base + IPCMxDCLEAR(mbox));
  57        writel_relaxed(CHAN_MASK(source), ipc_base + IPCMxMCLEAR(mbox));
  58}
  59
  60static void __ipc_send(int mbox, u32 *data)
  61{
  62        int i;
  63        for (i = 0; i < 7; i++)
  64                writel_relaxed(data[i], ipc_base + IPCMxDR(mbox, i));
  65        writel_relaxed(0x1, ipc_base + IPCMxSEND(mbox));
  66}
  67
  68static u32 __ipc_rcv(int mbox, u32 *data)
  69{
  70        int i;
  71        for (i = 0; i < 7; i++)
  72                data[i] = readl_relaxed(ipc_base + IPCMxDR(mbox, i));
  73        return data[1];
  74}
  75
  76/* blocking implementation from the A9 side, not usable in interrupts! */
  77int pl320_ipc_transmit(u32 *data)
  78{
  79        int ret;
  80
  81        mutex_lock(&ipc_m1_lock);
  82
  83        init_completion(&ipc_completion);
  84        __ipc_send(IPC_TX_MBOX, data);
  85        ret = wait_for_completion_timeout(&ipc_completion,
  86                                          msecs_to_jiffies(1000));
  87        if (ret == 0) {
  88                ret = -ETIMEDOUT;
  89                goto out;
  90        }
  91
  92        ret = __ipc_rcv(IPC_TX_MBOX, data);
  93out:
  94        mutex_unlock(&ipc_m1_lock);
  95        return ret;
  96}
  97EXPORT_SYMBOL_GPL(pl320_ipc_transmit);
  98
  99static irqreturn_t ipc_handler(int irq, void *dev)
 100{
 101        u32 irq_stat;
 102        u32 data[7];
 103
 104        irq_stat = readl_relaxed(ipc_base + IPCMMIS(1));
 105        if (irq_stat & MBOX_MASK(IPC_TX_MBOX)) {
 106                writel_relaxed(0, ipc_base + IPCMxSEND(IPC_TX_MBOX));
 107                complete(&ipc_completion);
 108        }
 109        if (irq_stat & MBOX_MASK(IPC_RX_MBOX)) {
 110                __ipc_rcv(IPC_RX_MBOX, data);
 111                atomic_notifier_call_chain(&ipc_notifier, data[0], data + 1);
 112                writel_relaxed(2, ipc_base + IPCMxSEND(IPC_RX_MBOX));
 113        }
 114
 115        return IRQ_HANDLED;
 116}
 117
 118int pl320_ipc_register_notifier(struct notifier_block *nb)
 119{
 120        return atomic_notifier_chain_register(&ipc_notifier, nb);
 121}
 122EXPORT_SYMBOL_GPL(pl320_ipc_register_notifier);
 123
 124int pl320_ipc_unregister_notifier(struct notifier_block *nb)
 125{
 126        return atomic_notifier_chain_unregister(&ipc_notifier, nb);
 127}
 128EXPORT_SYMBOL_GPL(pl320_ipc_unregister_notifier);
 129
 130static int pl320_probe(struct amba_device *adev, const struct amba_id *id)
 131{
 132        int ret;
 133
 134        ipc_base = ioremap(adev->res.start, resource_size(&adev->res));
 135        if (ipc_base == NULL)
 136                return -ENOMEM;
 137
 138        writel_relaxed(0, ipc_base + IPCMxSEND(IPC_TX_MBOX));
 139
 140        ipc_irq = adev->irq[0];
 141        ret = request_irq(ipc_irq, ipc_handler, 0, dev_name(&adev->dev), NULL);
 142        if (ret < 0)
 143                goto err;
 144
 145        /* Init slow mailbox */
 146        writel_relaxed(CHAN_MASK(A9_SOURCE),
 147                       ipc_base + IPCMxSOURCE(IPC_TX_MBOX));
 148        writel_relaxed(CHAN_MASK(M3_SOURCE),
 149                       ipc_base + IPCMxDSET(IPC_TX_MBOX));
 150        writel_relaxed(CHAN_MASK(M3_SOURCE) | CHAN_MASK(A9_SOURCE),
 151                       ipc_base + IPCMxMSET(IPC_TX_MBOX));
 152
 153        /* Init receive mailbox */
 154        writel_relaxed(CHAN_MASK(M3_SOURCE),
 155                       ipc_base + IPCMxSOURCE(IPC_RX_MBOX));
 156        writel_relaxed(CHAN_MASK(A9_SOURCE),
 157                       ipc_base + IPCMxDSET(IPC_RX_MBOX));
 158        writel_relaxed(CHAN_MASK(M3_SOURCE) | CHAN_MASK(A9_SOURCE),
 159                       ipc_base + IPCMxMSET(IPC_RX_MBOX));
 160
 161        return 0;
 162err:
 163        iounmap(ipc_base);
 164        return ret;
 165}
 166
 167static struct amba_id pl320_ids[] = {
 168        {
 169                .id     = 0x00041320,
 170                .mask   = 0x000fffff,
 171        },
 172        { 0, 0 },
 173};
 174
 175static struct amba_driver pl320_driver = {
 176        .drv = {
 177                .name   = "pl320",
 178        },
 179        .id_table       = pl320_ids,
 180        .probe          = pl320_probe,
 181};
 182
 183static int __init ipc_init(void)
 184{
 185        return amba_driver_register(&pl320_driver);
 186}
 187subsys_initcall(ipc_init);
 188