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