mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 05:32:00 +00:00
e9803aac50
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it as merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Signed-off-by: Rob Herring <robh@kernel.org> Signed-off-by: Jassi Brar <jaswinder.singh@linaro.org>
299 lines
7.4 KiB
C
299 lines
7.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (c) 2017-2018 HiSilicon Limited.
|
|
// Copyright (c) 2017-2018 Linaro Limited.
|
|
|
|
#include <linux/bitops.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/mailbox_controller.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "mailbox.h"
|
|
|
|
#define MBOX_CHAN_MAX 32
|
|
|
|
#define MBOX_RX 0x0
|
|
#define MBOX_TX 0x1
|
|
|
|
#define MBOX_BASE(mbox, ch) ((mbox)->base + ((ch) * 0x40))
|
|
#define MBOX_SRC_REG 0x00
|
|
#define MBOX_DST_REG 0x04
|
|
#define MBOX_DCLR_REG 0x08
|
|
#define MBOX_DSTAT_REG 0x0c
|
|
#define MBOX_MODE_REG 0x10
|
|
#define MBOX_IMASK_REG 0x14
|
|
#define MBOX_ICLR_REG 0x18
|
|
#define MBOX_SEND_REG 0x1c
|
|
#define MBOX_DATA_REG 0x20
|
|
|
|
#define MBOX_IPC_LOCK_REG 0xa00
|
|
#define MBOX_IPC_UNLOCK 0x1acce551
|
|
|
|
#define MBOX_AUTOMATIC_ACK 1
|
|
|
|
#define MBOX_STATE_IDLE BIT(4)
|
|
#define MBOX_STATE_READY BIT(5)
|
|
#define MBOX_STATE_ACK BIT(7)
|
|
|
|
#define MBOX_MSG_LEN 8
|
|
|
|
/**
|
|
* struct hi3660_chan_info - Hi3660 mailbox channel information
|
|
* @dst_irq: Interrupt vector for remote processor
|
|
* @ack_irq: Interrupt vector for local processor
|
|
*
|
|
* A channel can be used for TX or RX, it can trigger remote
|
|
* processor interrupt to notify remote processor and can receive
|
|
* interrupt if it has an incoming message.
|
|
*/
|
|
struct hi3660_chan_info {
|
|
unsigned int dst_irq;
|
|
unsigned int ack_irq;
|
|
};
|
|
|
|
/**
|
|
* struct hi3660_mbox - Hi3660 mailbox controller data
|
|
* @dev: Device to which it is attached
|
|
* @base: Base address of the register mapping region
|
|
* @chan: Representation of channels in mailbox controller
|
|
* @mchan: Representation of channel info
|
|
* @controller: Representation of a communication channel controller
|
|
*
|
|
* Mailbox controller includes 32 channels and can allocate
|
|
* channel for message transferring.
|
|
*/
|
|
struct hi3660_mbox {
|
|
struct device *dev;
|
|
void __iomem *base;
|
|
struct mbox_chan chan[MBOX_CHAN_MAX];
|
|
struct hi3660_chan_info mchan[MBOX_CHAN_MAX];
|
|
struct mbox_controller controller;
|
|
};
|
|
|
|
static struct hi3660_mbox *to_hi3660_mbox(struct mbox_controller *mbox)
|
|
{
|
|
return container_of(mbox, struct hi3660_mbox, controller);
|
|
}
|
|
|
|
static int hi3660_mbox_check_state(struct mbox_chan *chan)
|
|
{
|
|
unsigned long ch = (unsigned long)chan->con_priv;
|
|
struct hi3660_mbox *mbox = to_hi3660_mbox(chan->mbox);
|
|
struct hi3660_chan_info *mchan = &mbox->mchan[ch];
|
|
void __iomem *base = MBOX_BASE(mbox, ch);
|
|
unsigned long val;
|
|
unsigned int ret;
|
|
|
|
/* Mailbox is ready to use */
|
|
if (readl(base + MBOX_MODE_REG) & MBOX_STATE_READY)
|
|
return 0;
|
|
|
|
/* Wait for acknowledge from remote */
|
|
ret = readx_poll_timeout_atomic(readl, base + MBOX_MODE_REG,
|
|
val, (val & MBOX_STATE_ACK), 1000, 300000);
|
|
if (ret) {
|
|
dev_err(mbox->dev, "%s: timeout for receiving ack\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/* clear ack state, mailbox will get back to ready state */
|
|
writel(BIT(mchan->ack_irq), base + MBOX_ICLR_REG);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hi3660_mbox_unlock(struct mbox_chan *chan)
|
|
{
|
|
struct hi3660_mbox *mbox = to_hi3660_mbox(chan->mbox);
|
|
unsigned int val, retry = 3;
|
|
|
|
do {
|
|
writel(MBOX_IPC_UNLOCK, mbox->base + MBOX_IPC_LOCK_REG);
|
|
|
|
val = readl(mbox->base + MBOX_IPC_LOCK_REG);
|
|
if (!val)
|
|
break;
|
|
|
|
udelay(10);
|
|
} while (retry--);
|
|
|
|
if (val)
|
|
dev_err(mbox->dev, "%s: failed to unlock mailbox\n", __func__);
|
|
|
|
return (!val) ? 0 : -ETIMEDOUT;
|
|
}
|
|
|
|
static int hi3660_mbox_acquire_channel(struct mbox_chan *chan)
|
|
{
|
|
unsigned long ch = (unsigned long)chan->con_priv;
|
|
struct hi3660_mbox *mbox = to_hi3660_mbox(chan->mbox);
|
|
struct hi3660_chan_info *mchan = &mbox->mchan[ch];
|
|
void __iomem *base = MBOX_BASE(mbox, ch);
|
|
unsigned int val, retry;
|
|
|
|
for (retry = 10; retry; retry--) {
|
|
/* Check if channel is in idle state */
|
|
if (readl(base + MBOX_MODE_REG) & MBOX_STATE_IDLE) {
|
|
writel(BIT(mchan->ack_irq), base + MBOX_SRC_REG);
|
|
|
|
/* Check ack bit has been set successfully */
|
|
val = readl(base + MBOX_SRC_REG);
|
|
if (val & BIT(mchan->ack_irq))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!retry)
|
|
dev_err(mbox->dev, "%s: failed to acquire channel\n", __func__);
|
|
|
|
return retry ? 0 : -ETIMEDOUT;
|
|
}
|
|
|
|
static int hi3660_mbox_startup(struct mbox_chan *chan)
|
|
{
|
|
int ret;
|
|
|
|
ret = hi3660_mbox_unlock(chan);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = hi3660_mbox_acquire_channel(chan);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hi3660_mbox_send_data(struct mbox_chan *chan, void *msg)
|
|
{
|
|
unsigned long ch = (unsigned long)chan->con_priv;
|
|
struct hi3660_mbox *mbox = to_hi3660_mbox(chan->mbox);
|
|
struct hi3660_chan_info *mchan = &mbox->mchan[ch];
|
|
void __iomem *base = MBOX_BASE(mbox, ch);
|
|
u32 *buf = msg;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ret = hi3660_mbox_check_state(chan);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Clear mask for destination interrupt */
|
|
writel_relaxed(~BIT(mchan->dst_irq), base + MBOX_IMASK_REG);
|
|
|
|
/* Config destination for interrupt vector */
|
|
writel_relaxed(BIT(mchan->dst_irq), base + MBOX_DST_REG);
|
|
|
|
/* Automatic acknowledge mode */
|
|
writel_relaxed(MBOX_AUTOMATIC_ACK, base + MBOX_MODE_REG);
|
|
|
|
/* Fill message data */
|
|
for (i = 0; i < MBOX_MSG_LEN; i++)
|
|
writel_relaxed(buf[i], base + MBOX_DATA_REG + i * 4);
|
|
|
|
/* Trigger data transferring */
|
|
writel(BIT(mchan->ack_irq), base + MBOX_SEND_REG);
|
|
return 0;
|
|
}
|
|
|
|
static const struct mbox_chan_ops hi3660_mbox_ops = {
|
|
.startup = hi3660_mbox_startup,
|
|
.send_data = hi3660_mbox_send_data,
|
|
};
|
|
|
|
static struct mbox_chan *hi3660_mbox_xlate(struct mbox_controller *controller,
|
|
const struct of_phandle_args *spec)
|
|
{
|
|
struct hi3660_mbox *mbox = to_hi3660_mbox(controller);
|
|
struct hi3660_chan_info *mchan;
|
|
unsigned int ch = spec->args[0];
|
|
|
|
if (ch >= MBOX_CHAN_MAX) {
|
|
dev_err(mbox->dev, "Invalid channel idx %d\n", ch);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
mchan = &mbox->mchan[ch];
|
|
mchan->dst_irq = spec->args[1];
|
|
mchan->ack_irq = spec->args[2];
|
|
|
|
return &mbox->chan[ch];
|
|
}
|
|
|
|
static const struct of_device_id hi3660_mbox_of_match[] = {
|
|
{ .compatible = "hisilicon,hi3660-mbox", },
|
|
{},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, hi3660_mbox_of_match);
|
|
|
|
static int hi3660_mbox_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct hi3660_mbox *mbox;
|
|
struct mbox_chan *chan;
|
|
unsigned long ch;
|
|
int err;
|
|
|
|
mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
|
|
if (!mbox)
|
|
return -ENOMEM;
|
|
|
|
mbox->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(mbox->base))
|
|
return PTR_ERR(mbox->base);
|
|
|
|
mbox->dev = dev;
|
|
mbox->controller.dev = dev;
|
|
mbox->controller.chans = mbox->chan;
|
|
mbox->controller.num_chans = MBOX_CHAN_MAX;
|
|
mbox->controller.ops = &hi3660_mbox_ops;
|
|
mbox->controller.of_xlate = hi3660_mbox_xlate;
|
|
|
|
/* Initialize mailbox channel data */
|
|
chan = mbox->chan;
|
|
for (ch = 0; ch < MBOX_CHAN_MAX; ch++)
|
|
chan[ch].con_priv = (void *)ch;
|
|
|
|
err = devm_mbox_controller_register(dev, &mbox->controller);
|
|
if (err) {
|
|
dev_err(dev, "Failed to register mailbox %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, mbox);
|
|
dev_info(dev, "Mailbox enabled\n");
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver hi3660_mbox_driver = {
|
|
.probe = hi3660_mbox_probe,
|
|
.driver = {
|
|
.name = "hi3660-mbox",
|
|
.of_match_table = hi3660_mbox_of_match,
|
|
},
|
|
};
|
|
|
|
static int __init hi3660_mbox_init(void)
|
|
{
|
|
return platform_driver_register(&hi3660_mbox_driver);
|
|
}
|
|
core_initcall(hi3660_mbox_init);
|
|
|
|
static void __exit hi3660_mbox_exit(void)
|
|
{
|
|
platform_driver_unregister(&hi3660_mbox_driver);
|
|
}
|
|
module_exit(hi3660_mbox_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Hisilicon Hi3660 Mailbox Controller");
|
|
MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>");
|