Commit 2b2f72d8 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'mailbox-for-next' of git://git.linaro.org/landing-teams/working/fujitsu/integration

Pull mailbox updates from Jassi Brar:

 - mailbox bindings and drivers for
     * APM X-Gene
     * Hisilicon Hi6220
     * Rockchip RK3368
   platforms

 - minor fixes to the above three drivers.

 - misc cleanups of mailbox-test driver.

* 'mailbox-for-next' of git://git.linaro.org/landing-teams/working/fujitsu/integration:
  mailbox: rockchip: avoid 64-bit division
  mailbox: rockchip: Add Rockchip mailbox driver
  dt-bindings: rockchip-mailbox: Add mailbox controller document on Rockchip SoCs
  mailbox/xgene-slimpro: Checking for IS_ERR instead of NULL
  mailbox: Hi6220: add mailbox driver
  dt-bindings: mailbox: Document Hi6220 mailbox driver
  mailbox: mailbox-test: add support for separate tx/rx buffer with single channel
  mailbox: mailbox-test: use print_hex_dump_bytes to allow dynamic printk
  mailbox: mailbox-test: fix the compatible string
  mailbox: mailbox-test: rename driver as generic test driver
  Documentation: mailbox: Add APM X-Gene SLIMpro mailbox dts documentation
  mailbox: Add support for APM X-Gene platform mailbox driver
parents fffad3e1 c5a9d1f3
Hisilicon Hi6220 Mailbox Driver
===============================
Hisilicon Hi6220 mailbox supports up to 32 channels. Each channel
is unidirectional with a maximum message size of 8 words. I/O is
performed using register access (there is no DMA) and the cell
raises an interrupt when messages are received.
Mailbox Device Node:
====================
Required properties:
--------------------
- compatible: Shall be "hisilicon,hi6220-mbox"
- reg: Contains the mailbox register address range (base
address and length); the first item is for IPC
registers, the second item is shared buffer for
slots.
- #mbox-cells: Common mailbox binding property to identify the number
of cells required for the mailbox specifier. Must be 3.
<&phandle slot_id dst_irq ack_irq>
phandle: Label name of mailbox controller
slot_id: Slot id used either for TX or RX
dst_irq: IRQ identifier index number which used by MCU
ack_irq: IRQ identifier index number with generating a
TX/RX interrupt to application processor,
mailbox driver uses it to acknowledge interrupt
- interrupts: Contains the interrupt information for the mailbox
device. The format is dependent on which interrupt
controller the SoCs use.
Optional Properties:
--------------------
- hi6220,mbox-tx-noirq: Property of MCU firmware's feature, so mailbox driver
use this flag to ask MCU to enable "automatic idle
flag" mode or IRQ generated mode to acknowledge a TX
completion.
Example:
--------
mailbox: mailbox@f7510000 {
compatible = "hisilicon,hi6220-mbox";
reg = <0x0 0xf7510000 0x0 0x1000>, /* IPC_S */
<0x0 0x06dff800 0x0 0x0800>; /* Mailbox */
interrupt-parent = <&gic>;
interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>;
#mbox-cells = <3>;
};
Mailbox client
===============
Required properties:
--------------------
- compatible: Many (See the client docs).
- mboxes: Standard property to specify a Mailbox (See ./mailbox.txt)
Cells must match 'mbox-cells' (See Mailbox Device Node above).
Optional Properties:
--------------------
- mbox-names: Name given to channels seen in the 'mboxes' property.
Example:
--------
stub_clock: stub_clock {
compatible = "hisilicon,hi6220-stub-clk";
hisilicon,hi6220-clk-sram = <&sram>;
#clock-cells = <1>;
mbox-names = "mbox-tx", "mbox-rx";
mboxes = <&mailbox 1 0 11>, <&mailbox 0 1 10>;
};
Rockchip mailbox
The Rockchip mailbox is used by the Rockchip CPU cores to communicate
requests to MCU processor.
Refer to ./mailbox.txt for generic information about mailbox device-tree
bindings.
Required properties:
- compatible: should be one of the following.
- "rockchip,rk3368-mbox" for rk3368
- reg: physical base address of the controller and length of memory mapped
region.
- interrupts: The interrupt number to the cpu. The interrupt specifier format
depends on the interrupt controller.
- #mbox-cells: Common mailbox binding property to identify the number
of cells required for the mailbox specifier. Should be 1
Example:
--------
/* RK3368 */
mbox: mbox@ff6b0000 {
compatible = "rockchip,rk3368-mailbox";
reg = <0x0 0xff6b0000 0x0 0x1000>,
interrupts = <GIC_SPI 146 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 147 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 149 IRQ_TYPE_LEVEL_HIGH>;
#mbox-cells = <1>;
};
...@@ -44,7 +44,7 @@ Optional properties ...@@ -44,7 +44,7 @@ Optional properties
Example: Example:
mailbox_test { mailbox_test {
compatible = "mailbox_test"; compatible = "mailbox-test";
reg = <0x[shared_memory_address], [shared_memory_size]>; reg = <0x[shared_memory_address], [shared_memory_size]>;
mboxes = <&mailbox2 0 1>, <&mailbox0 2 1>; mboxes = <&mailbox2 0 1>, <&mailbox0 2 1>;
mbox-names = "tx", "rx"; mbox-names = "tx", "rx";
......
The APM X-Gene SLIMpro mailbox is used to communicate messages between
the ARM64 processors and the Cortex M3 (dubbed SLIMpro). It uses a simple
interrupt based door bell mechanism and can exchange simple messages using the
internal registers.
There are total of 8 interrupts in this mailbox. Each used for an individual
door bell (or mailbox channel).
Required properties:
- compatible: Should be as "apm,xgene-slimpro-mbox".
- reg: Contains the mailbox register address range.
- interrupts: 8 interrupts must be from 0 to 7, interrupt 0 define the
the interrupt for mailbox channel 0 and interrupt 1 for
mailbox channel 1 and so likewise for the reminder.
- #mbox-cells: only one to specify the mailbox channel number.
Example:
Mailbox Node:
mailbox: mailbox@10540000 {
compatible = "apm,xgene-slimpro-mbox";
reg = <0x0 0x10540000 0x0 0xa000>;
#mbox-cells = <1>;
interrupts = <0x0 0x0 0x4>,
<0x0 0x1 0x4>,
<0x0 0x2 0x4>,
<0x0 0x3 0x4>,
<0x0 0x4 0x4>,
<0x0 0x5 0x4>,
<0x0 0x6 0x4>,
<0x0 0x7 0x4>,
};
...@@ -43,6 +43,15 @@ config OMAP_MBOX_KFIFO_SIZE ...@@ -43,6 +43,15 @@ config OMAP_MBOX_KFIFO_SIZE
This can also be changed at runtime (via the mbox_kfifo_size This can also be changed at runtime (via the mbox_kfifo_size
module parameter). module parameter).
config ROCKCHIP_MBOX
bool "Rockchip Soc Intergrated Mailbox Support"
depends on ARCH_ROCKCHIP || COMPILE_TEST
help
This driver provides support for inter-processor communication
between CPU cores and MCU processor on Some Rockchip SOCs.
Please check it that the Soc you use have Mailbox hardware.
Say Y here if you want to use the Rockchip Mailbox support.
config PCC config PCC
bool "Platform Communication Channel Driver" bool "Platform Communication Channel Driver"
depends on ACPI depends on ACPI
...@@ -78,6 +87,14 @@ config STI_MBOX ...@@ -78,6 +87,14 @@ config STI_MBOX
Mailbox implementation for STMicroelectonics family chips with Mailbox implementation for STMicroelectonics family chips with
hardware for interprocessor communication. hardware for interprocessor communication.
config HI6220_MBOX
tristate "Hi6220 Mailbox"
depends on ARCH_HISI
help
An implementation of the hi6220 mailbox. It is used to send message
between application processors and MCU. Say Y here if you want to
build Hi6220 mailbox controller driver.
config MAILBOX_TEST config MAILBOX_TEST
tristate "Mailbox Test Client" tristate "Mailbox Test Client"
depends on OF depends on OF
...@@ -86,4 +103,13 @@ config MAILBOX_TEST ...@@ -86,4 +103,13 @@ config MAILBOX_TEST
Test client to help with testing new Controller driver Test client to help with testing new Controller driver
implementations. implementations.
config XGENE_SLIMPRO_MBOX
tristate "APM SoC X-Gene SLIMpro Mailbox Controller"
depends on ARCH_XGENE
help
An implementation of the APM X-Gene Interprocessor Communication
Mailbox (IPCM) between the ARM 64-bit cores and SLIMpro controller.
It is used to send short messages between ARM64-bit cores and
the SLIMpro Management Engine, primarily for PM. Say Y here if you
want to use the APM X-Gene SLIMpro IPCM support.
endif endif
...@@ -10,6 +10,8 @@ obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o ...@@ -10,6 +10,8 @@ obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o
obj-$(CONFIG_OMAP2PLUS_MBOX) += omap-mailbox.o obj-$(CONFIG_OMAP2PLUS_MBOX) += omap-mailbox.o
obj-$(CONFIG_ROCKCHIP_MBOX) += rockchip-mailbox.o
obj-$(CONFIG_PCC) += pcc.o obj-$(CONFIG_PCC) += pcc.o
obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o
...@@ -17,3 +19,7 @@ obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o ...@@ -17,3 +19,7 @@ obj-$(CONFIG_ALTERA_MBOX) += mailbox-altera.o
obj-$(CONFIG_BCM2835_MBOX) += bcm2835-mailbox.o obj-$(CONFIG_BCM2835_MBOX) += bcm2835-mailbox.o
obj-$(CONFIG_STI_MBOX) += mailbox-sti.o obj-$(CONFIG_STI_MBOX) += mailbox-sti.o
obj-$(CONFIG_XGENE_SLIMPRO_MBOX) += mailbox-xgene-slimpro.o
obj-$(CONFIG_HI6220_MBOX) += hi6220-mailbox.o
/*
* Hisilicon's Hi6220 mailbox driver
*
* Copyright (c) 2015 Hisilicon Limited.
* Copyright (c) 2015 Linaro Limited.
*
* Author: Leo Yan <leo.yan@linaro.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kfifo.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define MBOX_CHAN_MAX 32
#define MBOX_TX 0x1
/* Mailbox message length: 8 words */
#define MBOX_MSG_LEN 8
/* Mailbox Registers */
#define MBOX_OFF(m) (0x40 * (m))
#define MBOX_MODE_REG(m) (MBOX_OFF(m) + 0x0)
#define MBOX_DATA_REG(m) (MBOX_OFF(m) + 0x4)
#define MBOX_STATE_MASK (0xF << 4)
#define MBOX_STATE_IDLE (0x1 << 4)
#define MBOX_STATE_TX (0x2 << 4)
#define MBOX_STATE_RX (0x4 << 4)
#define MBOX_STATE_ACK (0x8 << 4)
#define MBOX_ACK_CONFIG_MASK (0x1 << 0)
#define MBOX_ACK_AUTOMATIC (0x1 << 0)
#define MBOX_ACK_IRQ (0x0 << 0)
/* IPC registers */
#define ACK_INT_RAW_REG(i) ((i) + 0x400)
#define ACK_INT_MSK_REG(i) ((i) + 0x404)
#define ACK_INT_STAT_REG(i) ((i) + 0x408)
#define ACK_INT_CLR_REG(i) ((i) + 0x40c)
#define ACK_INT_ENA_REG(i) ((i) + 0x500)
#define ACK_INT_DIS_REG(i) ((i) + 0x504)
#define DST_INT_RAW_REG(i) ((i) + 0x420)
struct hi6220_mbox_chan {
/*
* Description for channel's hardware info:
* - direction: tx or rx
* - dst irq: peer core's irq number
* - ack irq: local irq number
* - slot number
*/
unsigned int dir, dst_irq, ack_irq;
unsigned int slot;
struct hi6220_mbox *parent;
};
struct hi6220_mbox {
struct device *dev;
int irq;
/* flag of enabling tx's irq mode */
bool tx_irq_mode;
/* region for ipc event */
void __iomem *ipc;
/* region for mailbox */
void __iomem *base;
unsigned int chan_num;
struct hi6220_mbox_chan *mchan;
void *irq_map_chan[MBOX_CHAN_MAX];
struct mbox_chan *chan;
struct mbox_controller controller;
};
static void mbox_set_state(struct hi6220_mbox *mbox,
unsigned int slot, u32 val)
{
u32 status;
status = readl(mbox->base + MBOX_MODE_REG(slot));
status = (status & ~MBOX_STATE_MASK) | val;
writel(status, mbox->base + MBOX_MODE_REG(slot));
}
static void mbox_set_mode(struct hi6220_mbox *mbox,
unsigned int slot, u32 val)
{
u32 mode;
mode = readl(mbox->base + MBOX_MODE_REG(slot));
mode = (mode & ~MBOX_ACK_CONFIG_MASK) | val;
writel(mode, mbox->base + MBOX_MODE_REG(slot));
}
static bool hi6220_mbox_last_tx_done(struct mbox_chan *chan)
{
struct hi6220_mbox_chan *mchan = chan->con_priv;
struct hi6220_mbox *mbox = mchan->parent;
u32 state;
/* Only set idle state for polling mode */
BUG_ON(mbox->tx_irq_mode);
state = readl(mbox->base + MBOX_MODE_REG(mchan->slot));
return ((state & MBOX_STATE_MASK) == MBOX_STATE_IDLE);
}
static int hi6220_mbox_send_data(struct mbox_chan *chan, void *msg)
{
struct hi6220_mbox_chan *mchan = chan->con_priv;
struct hi6220_mbox *mbox = mchan->parent;
unsigned int slot = mchan->slot;
u32 *buf = msg;
int i;
/* indicate as a TX channel */
mchan->dir = MBOX_TX;
mbox_set_state(mbox, slot, MBOX_STATE_TX);
if (mbox->tx_irq_mode)
mbox_set_mode(mbox, slot, MBOX_ACK_IRQ);
else
mbox_set_mode(mbox, slot, MBOX_ACK_AUTOMATIC);
for (i = 0; i < MBOX_MSG_LEN; i++)
writel(buf[i], mbox->base + MBOX_DATA_REG(slot) + i * 4);
/* trigger remote request */
writel(BIT(mchan->dst_irq), DST_INT_RAW_REG(mbox->ipc));
return 0;
}
static irqreturn_t hi6220_mbox_interrupt(int irq, void *p)
{
struct hi6220_mbox *mbox = p;
struct hi6220_mbox_chan *mchan;
struct mbox_chan *chan;
unsigned int state, intr_bit, i;
u32 msg[MBOX_MSG_LEN];
state = readl(ACK_INT_STAT_REG(mbox->ipc));
if (!state) {
dev_warn(mbox->dev, "%s: spurious interrupt\n",
__func__);
return IRQ_HANDLED;
}
while (state) {
intr_bit = __ffs(state);
state &= (state - 1);
chan = mbox->irq_map_chan[intr_bit];
if (!chan) {
dev_warn(mbox->dev, "%s: unexpected irq vector %d\n",
__func__, intr_bit);
continue;
}
mchan = chan->con_priv;
if (mchan->dir == MBOX_TX)
mbox_chan_txdone(chan, 0);
else {
for (i = 0; i < MBOX_MSG_LEN; i++)
msg[i] = readl(mbox->base +
MBOX_DATA_REG(mchan->slot) + i * 4);
mbox_chan_received_data(chan, (void *)msg);
}
/* clear IRQ source */
writel(BIT(mchan->ack_irq), ACK_INT_CLR_REG(mbox->ipc));
mbox_set_state(mbox, mchan->slot, MBOX_STATE_IDLE);
}
return IRQ_HANDLED;
}
static int hi6220_mbox_startup(struct mbox_chan *chan)
{
struct hi6220_mbox_chan *mchan = chan->con_priv;
struct hi6220_mbox *mbox = mchan->parent;
mchan->dir = 0;
/* enable interrupt */
writel(BIT(mchan->ack_irq), ACK_INT_ENA_REG(mbox->ipc));
return 0;
}
static void hi6220_mbox_shutdown(struct mbox_chan *chan)
{
struct hi6220_mbox_chan *mchan = chan->con_priv;
struct hi6220_mbox *mbox = mchan->parent;
/* disable interrupt */
writel(BIT(mchan->ack_irq), ACK_INT_DIS_REG(mbox->ipc));
mbox->irq_map_chan[mchan->ack_irq] = NULL;
}
static struct mbox_chan_ops hi6220_mbox_ops = {
.send_data = hi6220_mbox_send_data,
.startup = hi6220_mbox_startup,
.shutdown = hi6220_mbox_shutdown,
.last_tx_done = hi6220_mbox_last_tx_done,
};
static struct mbox_chan *hi6220_mbox_xlate(struct mbox_controller *controller,
const struct of_phandle_args *spec)
{
struct hi6220_mbox *mbox = dev_get_drvdata(controller->dev);
struct hi6220_mbox_chan *mchan;
struct mbox_chan *chan;
unsigned int i = spec->args[0];
unsigned int dst_irq = spec->args[1];
unsigned int ack_irq = spec->args[2];
/* Bounds checking */
if (i >= mbox->chan_num || dst_irq >= mbox->chan_num ||
ack_irq >= mbox->chan_num) {
dev_err(mbox->dev,
"Invalid channel idx %d dst_irq %d ack_irq %d\n",
i, dst_irq, ack_irq);
return ERR_PTR(-EINVAL);
}
/* Is requested channel free? */
chan = &mbox->chan[i];
if (mbox->irq_map_chan[ack_irq] == (void *)chan) {
dev_err(mbox->dev, "Channel in use\n");
return ERR_PTR(-EBUSY);
}
mchan = chan->con_priv;
mchan->dst_irq = dst_irq;
mchan->ack_irq = ack_irq;
mbox->irq_map_chan[ack_irq] = (void *)chan;
return chan;
}
static const struct of_device_id hi6220_mbox_of_match[] = {
{ .compatible = "hisilicon,hi6220-mbox", },
{},
};
MODULE_DEVICE_TABLE(of, hi6220_mbox_of_match);
static int hi6220_mbox_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct hi6220_mbox *mbox;
struct resource *res;
int i, err;
mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
if (!mbox)
return -ENOMEM;
mbox->dev = dev;
mbox->chan_num = MBOX_CHAN_MAX;
mbox->mchan = devm_kzalloc(dev,
mbox->chan_num * sizeof(*mbox->mchan), GFP_KERNEL);
if (!mbox->mchan)
return -ENOMEM;
mbox->chan = devm_kzalloc(dev,
mbox->chan_num * sizeof(*mbox->chan), GFP_KERNEL);
if (!mbox->chan)
return -ENOMEM;
mbox->irq = platform_get_irq(pdev, 0);
if (mbox->irq < 0)
return mbox->irq;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mbox->ipc = devm_ioremap_resource(dev, res);
if (IS_ERR(mbox->ipc)) {
dev_err(dev, "ioremap ipc failed\n");
return PTR_ERR(mbox->ipc);
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
mbox->base = devm_ioremap_resource(dev, res);
if (IS_ERR(mbox->base)) {
dev_err(dev, "ioremap buffer failed\n");
return PTR_ERR(mbox->base);
}
err = devm_request_irq(dev, mbox->irq, hi6220_mbox_interrupt, 0,
dev_name(dev), mbox);
if (err) {
dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
err);
return -ENODEV;
}
mbox->controller.dev = dev;
mbox->controller.chans = &mbox->chan[0];
mbox->controller.num_chans = mbox->chan_num;
mbox->controller.ops = &hi6220_mbox_ops;
mbox->controller.of_xlate = hi6220_mbox_xlate;
for (i = 0; i < mbox->chan_num; i++) {
mbox->chan[i].con_priv = &mbox->mchan[i];
mbox->irq_map_chan[i] = NULL;
mbox->mchan[i].parent = mbox;
mbox->mchan[i].slot = i;
}
/* mask and clear all interrupt vectors */
writel(0x0, ACK_INT_MSK_REG(mbox->ipc));
writel(~0x0, ACK_INT_CLR_REG(mbox->ipc));
/* use interrupt for tx's ack */
if (of_find_property(node, "hi6220,mbox-tx-noirq", NULL))
mbox->tx_irq_mode = false;
else
mbox->tx_irq_mode = true;
if (mbox->tx_irq_mode)
mbox->controller.txdone_irq = true;
else {
mbox->controller.txdone_poll = true;
mbox->controller.txpoll_period = 5;
}
err = mbox_controller_register(&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 int hi6220_mbox_remove(struct platform_device *pdev)
{
struct hi6220_mbox *mbox = platform_get_drvdata(pdev);
mbox_controller_unregister(&mbox->controller);
return 0;
}
static struct platform_driver hi6220_mbox_driver = {
.driver = {
.name = "hi6220-mbox",
.owner = THIS_MODULE,
.of_match_table = hi6220_mbox_of_match,
},
.probe = hi6220_mbox_probe,
.remove = hi6220_mbox_remove,
};
static int __init hi6220_mbox_init(void)
{
return platform_driver_register(&hi6220_mbox_driver);
}
core_initcall(hi6220_mbox_init);
static void __exit hi6220_mbox_exit(void)
{
platform_driver_unregister(&hi6220_mbox_driver);
}
module_exit(hi6220_mbox_exit);
MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>");
MODULE_DESCRIPTION("Hi6220 mailbox driver");
MODULE_LICENSE("GPL v2");
...@@ -31,7 +31,8 @@ static struct dentry *root_debugfs_dir; ...@@ -31,7 +31,8 @@ static struct dentry *root_debugfs_dir;
struct mbox_test_device { struct mbox_test_device {
struct device *dev; struct device *dev;
void __iomem *mmio; void __iomem *tx_mmio;
void __iomem *rx_mmio;
struct mbox_chan *tx_channel; struct mbox_chan *tx_channel;
struct mbox_chan *rx_channel; struct mbox_chan *rx_channel;
char *rx_buffer; char *rx_buffer;
...@@ -112,16 +113,16 @@ static ssize_t mbox_test_message_write(struct file *filp, ...@@ -112,16 +113,16 @@ static ssize_t mbox_test_message_write(struct file *filp,
* A separate signal is only of use if there is * A separate signal is only of use if there is
* MMIO to subsequently pass the message through * MMIO to subsequently pass the message through
*/ */
if (tdev->mmio && tdev->signal) { if (tdev->tx_mmio && tdev->signal) {
print_hex_dump(KERN_INFO, "Client: Sending: Signal: ", DUMP_PREFIX_ADDRESS, print_hex_dump_bytes("Client: Sending: Signal: ", DUMP_PREFIX_ADDRESS,
MBOX_BYTES_PER_LINE, 1, tdev->signal, MBOX_MAX_SIG_LEN, true); tdev->signal, MBOX_MAX_SIG_LEN);
data = tdev->signal; data = tdev->signal;
} else } else
data = tdev->message; data = tdev->message;
print_hex_dump(KERN_INFO, "Client: Sending: Message: ", DUMP_PREFIX_ADDRESS, print_hex_dump_bytes("Client: Sending: Message: ", DUMP_PREFIX_ADDRESS,
MBOX_BYTES_PER_LINE, 1, tdev->message, MBOX_MAX_MSG_LEN, true); tdev->message, MBOX_MAX_MSG_LEN);
ret = mbox_send_message(tdev->tx_channel, data); ret = mbox_send_message(tdev->tx_channel, data);
if (ret < 0) if (ret < 0)
...@@ -220,15 +221,13 @@ static void mbox_test_receive_message(struct mbox_client *client, void *message) ...@@ -220,15 +221,13 @@ static void mbox_test_receive_message(struct mbox_client *client, void *message)
unsigned long flags; unsigned long flags;
spin_lock_irqsave(&tdev->lock, flags); spin_lock_irqsave(&tdev->lock, flags);
if (tdev->mmio) { if (tdev->rx_mmio) {
memcpy_fromio(tdev->rx_buffer, tdev->mmio, MBOX_MAX_MSG_LEN); memcpy_fromio(tdev->rx_buffer, tdev->rx_mmio, MBOX_MAX_MSG_LEN);
print_hex_dump(KERN_INFO, "Client: Received [MMIO]: ", print_hex_dump_bytes("Client: Received [MMIO]: ", DUMP_PREFIX_ADDRESS,
DUMP_PREFIX_ADDRESS, MBOX_BYTES_PER_LINE, 1, tdev->rx_buffer, MBOX_MAX_MSG_LEN);
tdev->rx_buffer, MBOX_MAX_MSG_LEN, true);
} else if (message) { } else if (message) {
print_hex_dump(KERN_INFO, "Client: Received [API]: ", print_hex_dump_bytes("Client: Received [API]: ", DUMP_PREFIX_ADDRESS,
DUMP_PREFIX_ADDRESS, MBOX_BYTES_PER_LINE, 1, message, MBOX_MAX_MSG_LEN);
message, MBOX_MAX_MSG_LEN, true);
memcpy(tdev->rx_buffer, message, MBOX_MAX_MSG_LEN); memcpy(tdev->rx_buffer, message, MBOX_MAX_MSG_LEN);
} }
spin_unlock_irqrestore(&tdev->lock, flags); spin_unlock_irqrestore(&tdev->lock, flags);
...@@ -238,11 +237,11 @@ static void mbox_test_prepare_message(struct mbox_client *client, void *message) ...@@ -238,11 +237,11 @@ static void mbox_test_prepare_message(struct mbox_client *client, void *message)
{ {
struct mbox_test_device *tdev = dev_get_drvdata(client->dev); struct mbox_test_device *tdev = dev_get_drvdata(client->dev);
if (tdev->mmio) { if (tdev->tx_mmio) {
if (tdev->signal) if (tdev->signal)
memcpy_toio(tdev->mmio, tdev->message, MBOX_MAX_MSG_LEN); memcpy_toio(tdev->tx_mmio, tdev->message, MBOX_MAX_MSG_LEN);
else else
memcpy_toio(tdev->mmio, message, MBOX_MAX_MSG_LEN); memcpy_toio(tdev->tx_mmio, message, MBOX_MAX_MSG_LEN);
} }
} }
...@@ -296,9 +295,15 @@ static int mbox_test_probe(struct platform_device *pdev) ...@@ -296,9 +295,15 @@ static int mbox_test_probe(struct platform_device *pdev)
/* It's okay for MMIO to be NULL */ /* It's okay for MMIO to be NULL */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tdev->mmio = devm_ioremap_resource(&pdev->dev, res); tdev->tx_mmio = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(tdev->mmio)) if (IS_ERR(tdev->tx_mmio))
tdev->mmio = NULL; tdev->tx_mmio = NULL;
/* If specified, second reg entry is Rx MMIO */
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
tdev->rx_mmio = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(tdev->rx_mmio))
tdev->rx_mmio = tdev->tx_mmio;
tdev->tx_channel = mbox_test_request_channel(pdev, "tx"); tdev->tx_channel = mbox_test_request_channel(pdev, "tx");
tdev->rx_channel = mbox_test_request_channel(pdev, "rx"); tdev->rx_channel = mbox_test_request_channel(pdev, "rx");
...@@ -306,6 +311,10 @@ static int mbox_test_probe(struct platform_device *pdev) ...@@ -306,6 +311,10 @@ static int mbox_test_probe(struct platform_device *pdev)
if (!tdev->tx_channel && !tdev->rx_channel) if (!tdev->tx_channel && !tdev->rx_channel)
return -EPROBE_DEFER; return -EPROBE_DEFER;
/* If Rx is not specified but has Rx MMIO, then Rx = Tx */
if (!tdev->rx_channel && (tdev->rx_mmio != tdev->tx_mmio))
tdev->rx_channel = tdev->tx_channel;
tdev->dev = &pdev->dev; tdev->dev = &pdev->dev;
platform_set_drvdata(pdev, tdev); platform_set_drvdata(pdev, tdev);
...@@ -342,13 +351,13 @@ static int mbox_test_remove(struct platform_device *pdev) ...@@ -342,13 +351,13 @@ static int mbox_test_remove(struct platform_device *pdev)
} }
static const struct of_device_id mbox_test_match[] = { static const struct of_device_id mbox_test_match[] = {
{ .compatible = "mailbox_test" }, { .compatible = "mailbox-test" },
{}, {},
}; };
static struct platform_driver mbox_test_driver = { static struct platform_driver mbox_test_driver = {
.driver = { .driver = {
.name = "mailbox_sti_test", .name = "mailbox_test",
.of_match_table = mbox_test_match, .of_match_table = mbox_test_match,
}, },
.probe = mbox_test_probe, .probe = mbox_test_probe,
......
/*
* APM X-Gene SLIMpro MailBox Driver
*
* Copyright (c) 2015, Applied Micro Circuits Corporation
* Author: Feng Kan fkan@apm.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*/
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#define MBOX_CON_NAME "slimpro-mbox"
#define MBOX_REG_SET_OFFSET 0x1000
#define MBOX_CNT 8
#define MBOX_STATUS_AVAIL_MASK BIT(16)
#define MBOX_STATUS_ACK_MASK BIT(0)
/* Configuration and Status Registers */
#define REG_DB_IN 0x00
#define REG_DB_DIN0 0x04
#define REG_DB_DIN1 0x08
#define REG_DB_OUT 0x10
#define REG_DB_DOUT0 0x14
#define REG_DB_DOUT1 0x18
#define REG_DB_STAT 0x20
#define REG_DB_STATMASK 0x24
/**
* X-Gene SlimPRO mailbox channel information
*
* @dev: Device to which it is attached
* @chan: Pointer to mailbox communication channel
* @reg: Base address to access channel registers
* @irq: Interrupt number of the channel
* @rx_msg: Received message storage
*/
struct slimpro_mbox_chan {
struct device *dev;
struct mbox_chan *chan;
void __iomem *reg;
int irq;
u32 rx_msg[3];
};
/**
* X-Gene SlimPRO Mailbox controller data
*
* X-Gene SlimPRO Mailbox controller has 8 commnunication channels.
* Each channel has a separate IRQ number assgined to it.
*
* @mb_ctrl: Representation of the commnunication channel controller
* @mc: Array of SlimPRO mailbox channels of the controller
* @chans: Array of mailbox communication channels
*
*/
struct slimpro_mbox {
struct mbox_controller mb_ctrl;
struct slimpro_mbox_chan mc[MBOX_CNT];
struct mbox_chan chans[MBOX_CNT];
};
static void mb_chan_send_msg(struct slimpro_mbox_chan *mb_chan, u32 *msg)
{
writel(msg[1], mb_chan->reg + REG_DB_DOUT0);
writel(msg[2], mb_chan->reg + REG_DB_DOUT1);
writel(msg[0], mb_chan->reg + REG_DB_OUT);
}
static void mb_chan_recv_msg(struct slimpro_mbox_chan *mb_chan)
{
mb_chan->rx_msg[1] = readl(mb_chan->reg + REG_DB_DIN0);
mb_chan->rx_msg[2] = readl(mb_chan->reg + REG_DB_DIN1);
mb_chan->rx_msg[0] = readl(mb_chan->reg + REG_DB_IN);
}
static int mb_chan_status_ack(struct slimpro_mbox_chan *mb_chan)
{
u32 val = readl(mb_chan->reg + REG_DB_STAT);
if (val & MBOX_STATUS_ACK_MASK) {
writel(MBOX_STATUS_ACK_MASK, mb_chan->reg + REG_DB_STAT);
return 1;
}
return 0;
}
static int mb_chan_status_avail(struct slimpro_mbox_chan *mb_chan)
{
u32 val = readl(mb_chan->reg + REG_DB_STAT);
if (val & MBOX_STATUS_AVAIL_MASK) {
mb_chan_recv_msg(mb_chan);
writel(MBOX_STATUS_AVAIL_MASK, mb_chan->reg + REG_DB_STAT);
return 1;
}
return 0;
}
static irqreturn_t slimpro_mbox_irq(int irq, void *id)
{
struct slimpro_mbox_chan *mb_chan = id;
if (mb_chan_status_ack(mb_chan))
mbox_chan_txdone(mb_chan->chan, 0);
if (mb_chan_status_avail(mb_chan))
mbox_chan_received_data(mb_chan->chan, mb_chan->rx_msg);
return IRQ_HANDLED;
}
static int slimpro_mbox_send_data(struct mbox_chan *chan, void *msg)
{
struct slimpro_mbox_chan *mb_chan = chan->con_priv;
mb_chan_send_msg(mb_chan, msg);
return 0;
}
static int slimpro_mbox_startup(struct mbox_chan *chan)
{
struct slimpro_mbox_chan *mb_chan = chan->con_priv;
int rc;
u32 val;
rc = devm_request_irq(mb_chan->dev, mb_chan->irq, slimpro_mbox_irq, 0,
MBOX_CON_NAME, mb_chan);
if (unlikely(rc)) {
dev_err(mb_chan->dev, "failed to register mailbox interrupt %d\n",
mb_chan->irq);
return rc;
}
/* Enable HW interrupt */
writel(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK,
mb_chan->reg + REG_DB_STAT);
/* Unmask doorbell status interrupt */
val = readl(mb_chan->reg + REG_DB_STATMASK);
val &= ~(MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK);
writel(val, mb_chan->reg + REG_DB_STATMASK);
return 0;
}
static void slimpro_mbox_shutdown(struct mbox_chan *chan)
{
struct slimpro_mbox_chan *mb_chan = chan->con_priv;
u32 val;
/* Mask doorbell status interrupt */
val = readl(mb_chan->reg + REG_DB_STATMASK);
val |= (MBOX_STATUS_ACK_MASK | MBOX_STATUS_AVAIL_MASK);
writel(val, mb_chan->reg + REG_DB_STATMASK);
devm_free_irq(mb_chan->dev, mb_chan->irq, mb_chan);
}
static struct mbox_chan_ops slimpro_mbox_ops = {
.send_data = slimpro_mbox_send_data,
.startup = slimpro_mbox_startup,
.shutdown = slimpro_mbox_shutdown,
};
static int slimpro_mbox_probe(struct platform_device *pdev)
{
struct slimpro_mbox *ctx;
struct resource *regs;
void __iomem *mb_base;
int rc;
int i;
ctx = devm_kzalloc(&pdev->dev, sizeof(struct slimpro_mbox), GFP_KERNEL);
if (IS_ERR(ctx))
return PTR_ERR(ctx);
platform_set_drvdata(pdev, ctx);
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mb_base = devm_ioremap(&pdev->dev, regs->start, resource_size(regs));
if (!mb_base)
return -ENOMEM;
/* Setup mailbox links */
for (i = 0; i < MBOX_CNT; i++) {
ctx->mc[i].irq = platform_get_irq(pdev, i);
if (ctx->mc[i].irq < 0) {
if (i == 0) {
dev_err(&pdev->dev, "no available IRQ\n");
return -EINVAL;
}
dev_info(&pdev->dev, "no IRQ for channel %d\n", i);
break;
}
ctx->mc[i].dev = &pdev->dev;
ctx->mc[i].reg = mb_base + i * MBOX_REG_SET_OFFSET;
ctx->mc[i].chan = &ctx->chans[i];
ctx->chans[i].con_priv = &ctx->mc[i];
}
/* Setup mailbox controller */
ctx->mb_ctrl.dev = &pdev->dev;
ctx->mb_ctrl.chans = ctx->chans;
ctx->mb_ctrl.txdone_irq = true;
ctx->mb_ctrl.ops = &slimpro_mbox_ops;
ctx->mb_ctrl.num_chans = i;
rc = mbox_controller_register(&ctx->mb_ctrl);
if (rc) {
dev_err(&pdev->dev,
"APM X-Gene SLIMpro MailBox register failed:%d\n", rc);
return rc;
}
dev_info(&pdev->dev, "APM X-Gene SLIMpro MailBox registered\n");
return 0;
}
static int slimpro_mbox_remove(struct platform_device *pdev)
{
struct slimpro_mbox *smb = platform_get_drvdata(pdev);
mbox_controller_unregister(&smb->mb_ctrl);
return 0;
}
static const struct of_device_id slimpro_of_match[] = {
{.compatible = "apm,xgene-slimpro-mbox" },
{ },
};
MODULE_DEVICE_TABLE(of, slimpro_of_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id slimpro_acpi_ids[] = {
{"APMC0D01", 0},
{}
};
MODULE_DEVICE_TABLE(acpi, slimpro_acpi_ids);
#endif
static struct platform_driver slimpro_mbox_driver = {
.probe = slimpro_mbox_probe,
.remove = slimpro_mbox_remove,
.driver = {
.name = "xgene-slimpro-mbox",
.of_match_table = of_match_ptr(slimpro_of_match),
.acpi_match_table = ACPI_PTR(slimpro_acpi_ids)
},
};
static int __init slimpro_mbox_init(void)
{
return platform_driver_register(&slimpro_mbox_driver);
}
static void __exit slimpro_mbox_exit(void)
{
platform_driver_unregister(&slimpro_mbox_driver);
}
subsys_initcall(slimpro_mbox_init);
module_exit(slimpro_mbox_exit);
MODULE_DESCRIPTION("APM X-Gene SLIMpro Mailbox Driver");
MODULE_LICENSE("GPL");
/*
* Copyright (c) 2015, Fuzhou Rockchip Electronics Co., Ltd
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#define MAILBOX_A2B_INTEN 0x00
#define MAILBOX_A2B_STATUS 0x04
#define MAILBOX_A2B_CMD(x) (0x08 + (x) * 8)
#define MAILBOX_A2B_DAT(x) (0x0c + (x) * 8)
#define MAILBOX_B2A_INTEN 0x28
#define MAILBOX_B2A_STATUS 0x2C
#define MAILBOX_B2A_CMD(x) (0x30 + (x) * 8)
#define MAILBOX_B2A_DAT(x) (0x34 + (x) * 8)
struct rockchip_mbox_msg {
u32 cmd;
int rx_size;
};
struct rockchip_mbox_data {
int num_chans;
};
struct rockchip_mbox_chan {
int idx;
int irq;
struct rockchip_mbox_msg *msg;
struct rockchip_mbox *mb;
};
struct rockchip_mbox {
struct mbox_controller mbox;
struct clk *pclk;
void __iomem *mbox_base;
/* The maximum size of buf for each channel */
u32 buf_size;
struct rockchip_mbox_chan *chans;
};
static int rockchip_mbox_send_data(struct mbox_chan *chan, void *data)
{
struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev);
struct rockchip_mbox_msg *msg = data;
struct rockchip_mbox_chan *chans = mb->chans;
if (!msg)
return -EINVAL;
if (msg->rx_size > mb->buf_size) {
dev_err(mb->mbox.dev, "Transmit size over buf size(%d)\n",
mb->buf_size);
return -EINVAL;
}
dev_dbg(mb->mbox.dev, "Chan[%d]: A2B message, cmd 0x%08x\n",
chans->idx, msg->cmd);
mb->chans[chans->idx].msg = msg;
writel_relaxed(msg->cmd, mb->mbox_base + MAILBOX_A2B_CMD(chans->idx));
writel_relaxed(msg->rx_size, mb->mbox_base +
MAILBOX_A2B_DAT(chans->idx));
return 0;
}
static int rockchip_mbox_startup(struct mbox_chan *chan)
{
struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev);
/* Enable all B2A interrupts */
writel_relaxed((1 << mb->mbox.num_chans) - 1,
mb->mbox_base + MAILBOX_B2A_INTEN);
return 0;
}
static void rockchip_mbox_shutdown(struct mbox_chan *chan)
{
struct rockchip_mbox *mb = dev_get_drvdata(chan->mbox->dev);
struct rockchip_mbox_chan *chans = mb->chans;
/* Disable all B2A interrupts */
writel_relaxed(0, mb->mbox_base + MAILBOX_B2A_INTEN);
mb->chans[chans->idx].msg = NULL;
}
static const struct mbox_chan_ops rockchip_mbox_chan_ops = {
.send_data = rockchip_mbox_send_data,
.startup = rockchip_mbox_startup,
.shutdown = rockchip_mbox_shutdown,
};
static irqreturn_t rockchip_mbox_irq(int irq, void *dev_id)
{
int idx;
struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id;
u32 status = readl_relaxed(mb->mbox_base + MAILBOX_B2A_STATUS);
for (idx = 0; idx < mb->mbox.num_chans; idx++) {
if ((status & (1 << idx)) && (irq == mb->chans[idx].irq)) {
/* Clear mbox interrupt */
writel_relaxed(1 << idx,
mb->mbox_base + MAILBOX_B2A_STATUS);
return IRQ_WAKE_THREAD;
}
}
return IRQ_NONE;
}
static irqreturn_t rockchip_mbox_isr(int irq, void *dev_id)
{
int idx;
struct rockchip_mbox_msg *msg = NULL;
struct rockchip_mbox *mb = (struct rockchip_mbox *)dev_id;
for (idx = 0; idx < mb->mbox.num_chans; idx++) {
if (irq != mb->chans[idx].irq)
continue;
msg = mb->chans[idx].msg;
if (!msg) {
dev_err(mb->mbox.dev,
"Chan[%d]: B2A message is NULL\n", idx);
break; /* spurious */
}
mbox_chan_received_data(&mb->mbox.chans[idx], msg);
mb->chans[idx].msg = NULL;
dev_dbg(mb->mbox.dev, "Chan[%d]: B2A message, cmd 0x%08x\n",
idx, msg->cmd);
break;
}
return IRQ_HANDLED;
}
static const struct rockchip_mbox_data rk3368_drv_data = {
.num_chans = 4,
};
static const struct of_device_id rockchip_mbox_of_match[] = {
{ .compatible = "rockchip,rk3368-mailbox", .data = &rk3368_drv_data},
{ },
};
MODULE_DEVICE_TABLE(of, rockchp_mbox_of_match);
static int rockchip_mbox_probe(struct platform_device *pdev)
{
struct rockchip_mbox *mb;
const struct of_device_id *match;
const struct rockchip_mbox_data *drv_data;
struct resource *res;
int ret, irq, i;
if (!pdev->dev.of_node)
return -ENODEV;
match = of_match_node(rockchip_mbox_of_match, pdev->dev.of_node);
drv_data = (const struct rockchip_mbox_data *)match->data;
mb = devm_kzalloc(&pdev->dev, sizeof(*mb), GFP_KERNEL);
if (!mb)
return -ENOMEM;
mb->chans = devm_kcalloc(&pdev->dev, drv_data->num_chans,
sizeof(*mb->chans), GFP_KERNEL);
if (!mb->chans)
return -ENOMEM;
mb->mbox.chans = devm_kcalloc(&pdev->dev, drv_data->num_chans,
sizeof(*mb->mbox.chans), GFP_KERNEL);
if (!mb->mbox.chans)
return -ENOMEM;
platform_set_drvdata(pdev, mb);
mb->mbox.dev = &pdev->dev;
mb->mbox.num_chans = drv_data->num_chans;
mb->mbox.ops = &rockchip_mbox_chan_ops;
mb->mbox.txdone_irq = true;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
mb->mbox_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(mb->mbox_base))
return PTR_ERR(mb->mbox_base);
/* Each channel has two buffers for A2B and B2A */
mb->buf_size = (size_t)resource_size(res) / (drv_data->num_chans * 2);
mb->pclk = devm_clk_get(&pdev->dev, "pclk_mailbox");
if (IS_ERR(mb->pclk)) {
ret = PTR_ERR(mb->pclk);
dev_err(&pdev->dev, "failed to get pclk_mailbox clock: %d\n",
ret);
return ret;
}
ret = clk_prepare_enable(mb->pclk);
if (ret) {
dev_err(&pdev->dev, "failed to enable pclk: %d\n", ret);
return ret;
}
for (i = 0; i < mb->mbox.num_chans; i++) {
irq = platform_get_irq(pdev, i);
if (irq < 0)
return irq;
ret = devm_request_threaded_irq(&pdev->dev, irq,
rockchip_mbox_irq,
rockchip_mbox_isr, IRQF_ONESHOT,
dev_name(&pdev->dev), mb);
if (ret < 0)
return ret;
mb->chans[i].idx = i;
mb->chans[i].irq = irq;
mb->chans[i].mb = mb;
mb->chans[i].msg = NULL;
}
ret = mbox_controller_register(&mb->mbox);
if (ret < 0)
dev_err(&pdev->dev, "Failed to register mailbox: %d\n", ret);
return ret;
}
static int rockchip_mbox_remove(struct platform_device *pdev)
{
struct rockchip_mbox *mb = platform_get_drvdata(pdev);
if (!mb)
return -EINVAL;
mbox_controller_unregister(&mb->mbox);
return 0;
}
static struct platform_driver rockchip_mbox_driver = {
.probe = rockchip_mbox_probe,
.remove = rockchip_mbox_remove,
.driver = {
.name = "rockchip-mailbox",
.of_match_table = of_match_ptr(rockchip_mbox_of_match),
},
};
module_platform_driver(rockchip_mbox_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Rockchip mailbox: communicate between CPU cores and MCU");
MODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>");
MODULE_AUTHOR("Caesar Wang <wxt@rock-chips.com>");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment