Commit ca1a8680 authored by Cristian Marussi's avatar Cristian Marussi Committed by Jassi Brar

mailbox: arm_mhuv3: Add driver

Add support for ARM MHUv3 mailbox controller.

Support is limited to the MHUv3 Doorbell extension using only the PBX/MBX
combined interrupts.
Signed-off-by: default avatarCristian Marussi <cristian.marussi@arm.com>
Signed-off-by: default avatarJassi Brar <jassisinghbrar@gmail.com>
parent cd251970
...@@ -12994,6 +12994,15 @@ F: Documentation/devicetree/bindings/mailbox/arm,mhuv2.yaml ...@@ -12994,6 +12994,15 @@ F: Documentation/devicetree/bindings/mailbox/arm,mhuv2.yaml
F: drivers/mailbox/arm_mhuv2.c F: drivers/mailbox/arm_mhuv2.c
F: include/linux/mailbox/arm_mhuv2_message.h F: include/linux/mailbox/arm_mhuv2_message.h
MAILBOX ARM MHUv3
M: Sudeep Holla <sudeep.holla@arm.com>
M: Cristian Marussi <cristian.marussi@arm.com>
L: linux-kernel@vger.kernel.org
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
F: Documentation/devicetree/bindings/mailbox/arm,mhuv3.yaml
F: drivers/mailbox/arm_mhuv3.c
MAN-PAGES: MANUAL PAGES FOR LINUX -- Sections 2, 3, 4, 5, and 7 MAN-PAGES: MANUAL PAGES FOR LINUX -- Sections 2, 3, 4, 5, and 7
M: Alejandro Colomar <alx@kernel.org> M: Alejandro Colomar <alx@kernel.org>
L: linux-man@vger.kernel.org L: linux-man@vger.kernel.org
......
...@@ -23,6 +23,18 @@ config ARM_MHU_V2 ...@@ -23,6 +23,18 @@ config ARM_MHU_V2
Say Y here if you want to build the ARM MHUv2 controller driver, Say Y here if you want to build the ARM MHUv2 controller driver,
which provides unidirectional mailboxes between processing elements. which provides unidirectional mailboxes between processing elements.
config ARM_MHU_V3
tristate "ARM MHUv3 Mailbox"
depends on HAS_IOMEM || COMPILE_TEST
depends on OF
help
Say Y here if you want to build the ARM MHUv3 controller driver,
which provides unidirectional mailboxes between processing elements.
ARM MHUv3 controllers can implement a varying number of extensions
that provides different means of transports: supported extensions
will be discovered and possibly managed at probe-time.
config IMX_MBOX config IMX_MBOX
tristate "i.MX Mailbox" tristate "i.MX Mailbox"
depends on ARCH_MXC || COMPILE_TEST depends on ARCH_MXC || COMPILE_TEST
......
...@@ -9,6 +9,8 @@ obj-$(CONFIG_ARM_MHU) += arm_mhu.o arm_mhu_db.o ...@@ -9,6 +9,8 @@ obj-$(CONFIG_ARM_MHU) += arm_mhu.o arm_mhu_db.o
obj-$(CONFIG_ARM_MHU_V2) += arm_mhuv2.o obj-$(CONFIG_ARM_MHU_V2) += arm_mhuv2.o
obj-$(CONFIG_ARM_MHU_V3) += arm_mhuv3.o
obj-$(CONFIG_IMX_MBOX) += imx-mailbox.o obj-$(CONFIG_IMX_MBOX) += imx-mailbox.o
obj-$(CONFIG_ARMADA_37XX_RWTM_MBOX) += armada-37xx-rwtm-mailbox.o obj-$(CONFIG_ARMADA_37XX_RWTM_MBOX) += armada-37xx-rwtm-mailbox.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* ARM Message Handling Unit Version 3 (MHUv3) driver.
*
* Copyright (C) 2024 ARM Ltd.
*
* Based on ARM MHUv2 driver.
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/mailbox_controller.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/types.h>
/* ====== MHUv3 Registers ====== */
/* Maximum number of Doorbell channel windows */
#define MHUV3_DBCW_MAX 128
/* Number of DBCH combined interrupt status registers */
#define MHUV3_DBCH_CMB_INT_ST_REG_CNT 4
/* Number of FFCH combined interrupt status registers */
#define MHUV3_FFCH_CMB_INT_ST_REG_CNT 2
#define MHUV3_FLAG_BITS 32
/* Not a typo ... */
#define MHUV3_MAJOR_VERSION 2
enum {
MHUV3_MBOX_CELL_TYPE,
MHUV3_MBOX_CELL_CHWN,
MHUV3_MBOX_CELL_PARAM,
MHUV3_MBOX_CELLS
};
/* Padding bitfields/fields represents hole in the regs MMIO */
/* CTRL_Page */
struct blk_id {
#define id GENMASK(3, 0)
u32 val;
} __packed;
struct feat_spt0 {
#define dbe_spt GENMASK(3, 0)
#define fe_spt GENMASK(7, 4)
#define fce_spt GENMASK(11, 8)
u32 val;
} __packed;
struct feat_spt1 {
#define auto_op_spt GENMASK(3, 0)
u32 val;
} __packed;
struct dbch_cfg0 {
#define num_dbch GENMASK(7, 0)
u32 val;
} __packed;
struct ffch_cfg0 {
#define num_ffch GENMASK(7, 0)
#define x8ba_spt BIT(8)
#define x16ba_spt BIT(9)
#define x32ba_spt BIT(10)
#define x64ba_spt BIT(11)
#define ffch_depth GENMASK(25, 16)
u32 val;
} __packed;
struct fch_cfg0 {
#define num_fch GENMASK(9, 0)
#define fcgi_spt BIT(10) // MBX-only
#define num_fcg GENMASK(15, 11)
#define num_fch_per_grp GENMASK(20, 16)
#define fch_ws GENMASK(28, 21)
u32 val;
} __packed;
struct ctrl {
#define op_req BIT(0)
#define ch_op_mask BIT(1)
u32 val;
} __packed;
struct fch_ctrl {
#define _int_en BIT(2)
u32 val;
} __packed;
struct iidr {
#define implementer GENMASK(11, 0)
#define revision GENMASK(15, 12)
#define variant GENMASK(19, 16)
#define product_id GENMASK(31, 20)
u32 val;
} __packed;
struct aidr {
#define arch_minor_rev GENMASK(3, 0)
#define arch_major_rev GENMASK(7, 4)
u32 val;
} __packed;
struct ctrl_page {
struct blk_id blk_id;
u8 pad[12];
struct feat_spt0 feat_spt0;
struct feat_spt1 feat_spt1;
u8 pad1[8];
struct dbch_cfg0 dbch_cfg0;
u8 pad2[12];
struct ffch_cfg0 ffch_cfg0;
u8 pad3[12];
struct fch_cfg0 fch_cfg0;
u8 pad4[188];
struct ctrl x_ctrl;
/*-- MBX-only registers --*/
u8 pad5[60];
struct fch_ctrl fch_ctrl;
u32 fcg_int_en;
u8 pad6[696];
/*-- End of MBX-only ---- */
u32 dbch_int_st[MHUV3_DBCH_CMB_INT_ST_REG_CNT];
u32 ffch_int_st[MHUV3_FFCH_CMB_INT_ST_REG_CNT];
/*-- MBX-only registers --*/
u8 pad7[88];
u32 fcg_int_st;
u8 pad8[12];
u32 fcg_grp_int_st[32];
u8 pad9[2760];
/*-- End of MBX-only ---- */
struct iidr iidr;
struct aidr aidr;
u32 imp_def_id[12];
} __packed;
/* DBCW_Page */
struct xbcw_ctrl {
#define comb_en BIT(0)
u32 val;
} __packed;
struct pdbcw_int {
#define tfr_ack BIT(0)
u32 val;
} __packed;
struct pdbcw_page {
u32 st;
u8 pad[8];
u32 set;
struct pdbcw_int int_st;
struct pdbcw_int int_clr;
struct pdbcw_int int_en;
struct xbcw_ctrl ctrl;
} __packed;
struct mdbcw_page {
u32 st;
u32 st_msk;
u32 clr;
u8 pad[4];
u32 msk_st;
u32 msk_set;
u32 msk_clr;
struct xbcw_ctrl ctrl;
} __packed;
struct dummy_page {
u8 pad[SZ_4K];
} __packed;
struct mhu3_pbx_frame_reg {
struct ctrl_page ctrl;
struct pdbcw_page dbcw[MHUV3_DBCW_MAX];
struct dummy_page ffcw;
struct dummy_page fcw;
u8 pad[SZ_4K * 11];
struct dummy_page impdef;
} __packed;
struct mhu3_mbx_frame_reg {
struct ctrl_page ctrl;
struct mdbcw_page dbcw[MHUV3_DBCW_MAX];
struct dummy_page ffcw;
struct dummy_page fcw;
u8 pad[SZ_4K * 11];
struct dummy_page impdef;
} __packed;
/* Macro for reading a bitmask within a physically mapped packed struct */
#define readl_relaxed_bitmask(_regptr, _bitmask) \
({ \
unsigned long _rval; \
_rval = readl_relaxed(_regptr); \
FIELD_GET(_bitmask, _rval); \
})
/* Macro for writing a bitmask within a physically mapped packed struct */
#define writel_relaxed_bitmask(_value, _regptr, _bitmask) \
({ \
unsigned long _rval; \
typeof(_regptr) _rptr = _regptr; \
typeof(_bitmask) _bmask = _bitmask; \
_rval = readl_relaxed(_rptr); \
_rval &= ~(_bmask); \
_rval |= FIELD_PREP((unsigned long long)_bmask, _value);\
writel_relaxed(_rval, _rptr); \
})
/* ====== MHUv3 data structures ====== */
enum mhuv3_frame {
PBX_FRAME,
MBX_FRAME,
};
static char *mhuv3_str[] = {
"PBX",
"MBX"
};
enum mhuv3_extension_type {
DBE_EXT,
FCE_EXT,
FE_EXT,
NUM_EXT
};
static char *mhuv3_ext_str[] = {
"DBE",
"FCE",
"FE"
};
struct mhuv3;
/**
* struct mhuv3_protocol_ops - MHUv3 operations
*
* @rx_startup: Receiver startup callback.
* @rx_shutdown: Receiver shutdown callback.
* @read_data: Read available Sender in-band LE data (if any).
* @rx_complete: Acknowledge data reception to the Sender. Any out-of-band data
* has to have been already retrieved before calling this.
* @tx_startup: Sender startup callback.
* @tx_shutdown: Sender shutdown callback.
* @last_tx_done: Report back to the Sender if the last transfer has completed.
* @send_data: Send data to the receiver.
*
* Each supported transport protocol provides its own implementation of
* these operations.
*/
struct mhuv3_protocol_ops {
int (*rx_startup)(struct mhuv3 *mhu, struct mbox_chan *chan);
void (*rx_shutdown)(struct mhuv3 *mhu, struct mbox_chan *chan);
void *(*read_data)(struct mhuv3 *mhu, struct mbox_chan *chan);
void (*rx_complete)(struct mhuv3 *mhu, struct mbox_chan *chan);
void (*tx_startup)(struct mhuv3 *mhu, struct mbox_chan *chan);
void (*tx_shutdown)(struct mhuv3 *mhu, struct mbox_chan *chan);
int (*last_tx_done)(struct mhuv3 *mhu, struct mbox_chan *chan);
int (*send_data)(struct mhuv3 *mhu, struct mbox_chan *chan, void *arg);
};
/**
* struct mhuv3_mbox_chan_priv - MHUv3 channel private information
*
* @ch_idx: Channel window index associated to this mailbox channel.
* @doorbell: Doorbell bit number within the @ch_idx window.
* Only relevant to Doorbell transport.
* @ops: Transport protocol specific operations for this channel.
*
* Transport specific data attached to mmailbox channel priv data.
*/
struct mhuv3_mbox_chan_priv {
u32 ch_idx;
u32 doorbell;
const struct mhuv3_protocol_ops *ops;
};
/**
* struct mhuv3_extension - MHUv3 extension descriptor
*
* @type: Type of extension
* @num_chans: Max number of channels found for this extension.
* @base_ch_idx: First channel number assigned to this extension, picked from
* the set of all mailbox channels descriptors created.
* @mbox_of_xlate: Extension specific helper to parse DT and lookup associated
* channel from the related 'mboxes' property.
* @combined_irq_setup: Extension specific helper to setup the combined irq.
* @channels_init: Extension specific helper to initialize channels.
* @chan_from_comb_irq_get: Extension specific helper to lookup which channel
* triggered the combined irq.
* @pending_db: Array of per-channel pending doorbells.
* @pending_lock: Protect access to pending_db.
*/
struct mhuv3_extension {
enum mhuv3_extension_type type;
unsigned int num_chans;
unsigned int base_ch_idx;
struct mbox_chan *(*mbox_of_xlate)(struct mhuv3 *mhu,
unsigned int channel,
unsigned int param);
void (*combined_irq_setup)(struct mhuv3 *mhu);
int (*channels_init)(struct mhuv3 *mhu);
struct mbox_chan *(*chan_from_comb_irq_get)(struct mhuv3 *mhu);
u32 pending_db[MHUV3_DBCW_MAX];
/* Protect access to pending_db */
spinlock_t pending_lock;
};
/**
* struct mhuv3 - MHUv3 mailbox controller data
*
* @frame: Frame type: MBX_FRAME or PBX_FRAME.
* @auto_op_full: Flag to indicate if the MHU supports AutoOp full mode.
* @major: MHUv3 controller architectural major version.
* @minor: MHUv3 controller architectural minor version.
* @implem: MHUv3 controller IIDR implementer.
* @rev: MHUv3 controller IIDR revision.
* @var: MHUv3 controller IIDR variant.
* @prod_id: MHUv3 controller IIDR product_id.
* @num_chans: The total number of channnels discovered across all extensions.
* @cmb_irq: Combined IRQ number if any found defined.
* @ctrl: A reference to the MHUv3 control page for this block.
* @pbx: Base address of the PBX register mapping region.
* @mbx: Base address of the MBX register mapping region.
* @ext: Array holding descriptors for any found implemented extension.
* @mbox: Mailbox controller belonging to the MHU frame.
*/
struct mhuv3 {
enum mhuv3_frame frame;
bool auto_op_full;
unsigned int major;
unsigned int minor;
unsigned int implem;
unsigned int rev;
unsigned int var;
unsigned int prod_id;
unsigned int num_chans;
int cmb_irq;
struct ctrl_page __iomem *ctrl;
union {
struct mhu3_pbx_frame_reg __iomem *pbx;
struct mhu3_mbx_frame_reg __iomem *mbx;
};
struct mhuv3_extension *ext[NUM_EXT];
struct mbox_controller mbox;
};
#define mhu_from_mbox(_mbox) container_of(_mbox, struct mhuv3, mbox)
typedef int (*mhuv3_extension_initializer)(struct mhuv3 *mhu);
/* =================== Doorbell transport protocol operations =============== */
static void mhuv3_doorbell_tx_startup(struct mhuv3 *mhu, struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
/* Enable Transfer Acknowledgment events */
writel_relaxed_bitmask(0x1, &mhu->pbx->dbcw[priv->ch_idx].int_en, tfr_ack);
}
static void mhuv3_doorbell_tx_shutdown(struct mhuv3 *mhu, struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
struct mhuv3_extension *e = mhu->ext[DBE_EXT];
unsigned long flags;
/* Disable Channel Transfer Ack events */
writel_relaxed_bitmask(0x0, &mhu->pbx->dbcw[priv->ch_idx].int_en, tfr_ack);
/* Clear Channel Transfer Ack and pending doorbells */
writel_relaxed_bitmask(0x1, &mhu->pbx->dbcw[priv->ch_idx].int_clr, tfr_ack);
spin_lock_irqsave(&e->pending_lock, flags);
e->pending_db[priv->ch_idx] = 0;
spin_unlock_irqrestore(&e->pending_lock, flags);
}
static int mhuv3_doorbell_rx_startup(struct mhuv3 *mhu, struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
/* Unmask Channel Transfer events */
writel_relaxed(BIT(priv->doorbell), &mhu->mbx->dbcw[priv->ch_idx].msk_clr);
return 0;
}
static void mhuv3_doorbell_rx_shutdown(struct mhuv3 *mhu,
struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
/* Mask Channel Transfer events */
writel_relaxed(BIT(priv->doorbell), &mhu->mbx->dbcw[priv->ch_idx].msk_set);
}
static void mhuv3_doorbell_rx_complete(struct mhuv3 *mhu, struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
/* Clearing the pending transfer generates the Channel Transfer Ack */
writel_relaxed(BIT(priv->doorbell), &mhu->mbx->dbcw[priv->ch_idx].clr);
}
static int mhuv3_doorbell_last_tx_done(struct mhuv3 *mhu,
struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
int done;
done = !(readl_relaxed(&mhu->pbx->dbcw[priv->ch_idx].st) &
BIT(priv->doorbell));
if (done) {
struct mhuv3_extension *e = mhu->ext[DBE_EXT];
unsigned long flags;
/* Take care to clear the pending doorbell also when polling */
spin_lock_irqsave(&e->pending_lock, flags);
e->pending_db[priv->ch_idx] &= ~BIT(priv->doorbell);
spin_unlock_irqrestore(&e->pending_lock, flags);
}
return done;
}
static int mhuv3_doorbell_send_data(struct mhuv3 *mhu, struct mbox_chan *chan,
void *arg)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
struct mhuv3_extension *e = mhu->ext[DBE_EXT];
scoped_guard(spinlock_irqsave, &e->pending_lock) {
/* Only one in-flight Transfer is allowed per-doorbell */
if (e->pending_db[priv->ch_idx] & BIT(priv->doorbell))
return -EBUSY;
e->pending_db[priv->ch_idx] |= BIT(priv->doorbell);
}
writel_relaxed(BIT(priv->doorbell), &mhu->pbx->dbcw[priv->ch_idx].set);
return 0;
}
static const struct mhuv3_protocol_ops mhuv3_doorbell_ops = {
.tx_startup = mhuv3_doorbell_tx_startup,
.tx_shutdown = mhuv3_doorbell_tx_shutdown,
.rx_startup = mhuv3_doorbell_rx_startup,
.rx_shutdown = mhuv3_doorbell_rx_shutdown,
.rx_complete = mhuv3_doorbell_rx_complete,
.last_tx_done = mhuv3_doorbell_last_tx_done,
.send_data = mhuv3_doorbell_send_data,
};
/* Sender and receiver mailbox ops */
static bool mhuv3_sender_last_tx_done(struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
struct mhuv3 *mhu = mhu_from_mbox(chan->mbox);
return priv->ops->last_tx_done(mhu, chan);
}
static int mhuv3_sender_send_data(struct mbox_chan *chan, void *data)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
struct mhuv3 *mhu = mhu_from_mbox(chan->mbox);
if (!priv->ops->last_tx_done(mhu, chan))
return -EBUSY;
return priv->ops->send_data(mhu, chan, data);
}
static int mhuv3_sender_startup(struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
struct mhuv3 *mhu = mhu_from_mbox(chan->mbox);
if (priv->ops->tx_startup)
priv->ops->tx_startup(mhu, chan);
return 0;
}
static void mhuv3_sender_shutdown(struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
struct mhuv3 *mhu = mhu_from_mbox(chan->mbox);
if (priv->ops->tx_shutdown)
priv->ops->tx_shutdown(mhu, chan);
}
static const struct mbox_chan_ops mhuv3_sender_ops = {
.send_data = mhuv3_sender_send_data,
.startup = mhuv3_sender_startup,
.shutdown = mhuv3_sender_shutdown,
.last_tx_done = mhuv3_sender_last_tx_done,
};
static int mhuv3_receiver_startup(struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
struct mhuv3 *mhu = mhu_from_mbox(chan->mbox);
return priv->ops->rx_startup(mhu, chan);
}
static void mhuv3_receiver_shutdown(struct mbox_chan *chan)
{
struct mhuv3_mbox_chan_priv *priv = chan->con_priv;
struct mhuv3 *mhu = mhu_from_mbox(chan->mbox);
priv->ops->rx_shutdown(mhu, chan);
}
static int mhuv3_receiver_send_data(struct mbox_chan *chan, void *data)
{
dev_err(chan->mbox->dev,
"Trying to transmit on a MBX MHUv3 frame\n");
return -EIO;
}
static bool mhuv3_receiver_last_tx_done(struct mbox_chan *chan)
{
dev_err(chan->mbox->dev, "Trying to Tx poll on a MBX MHUv3 frame\n");
return true;
}
static const struct mbox_chan_ops mhuv3_receiver_ops = {
.send_data = mhuv3_receiver_send_data,
.startup = mhuv3_receiver_startup,
.shutdown = mhuv3_receiver_shutdown,
.last_tx_done = mhuv3_receiver_last_tx_done,
};
static struct mbox_chan *mhuv3_dbe_mbox_of_xlate(struct mhuv3 *mhu,
unsigned int channel,
unsigned int doorbell)
{
struct mhuv3_extension *e = mhu->ext[DBE_EXT];
struct mbox_controller *mbox = &mhu->mbox;
struct mbox_chan *chans = mbox->chans;
if (channel >= e->num_chans || doorbell >= MHUV3_FLAG_BITS) {
dev_err(mbox->dev, "Couldn't xlate to a valid channel (%d: %d)\n",
channel, doorbell);
return ERR_PTR(-ENODEV);
}
return &chans[e->base_ch_idx + channel * MHUV3_FLAG_BITS + doorbell];
}
static void mhuv3_dbe_combined_irq_setup(struct mhuv3 *mhu)
{
struct mhuv3_extension *e = mhu->ext[DBE_EXT];
int i;
if (mhu->frame == PBX_FRAME) {
struct pdbcw_page __iomem *dbcw = mhu->pbx->dbcw;
for (i = 0; i < e->num_chans; i++) {
writel_relaxed_bitmask(0x1, &dbcw[i].int_clr, tfr_ack);
writel_relaxed_bitmask(0x0, &dbcw[i].int_en, tfr_ack);
writel_relaxed_bitmask(0x1, &dbcw[i].ctrl, comb_en);
}
} else {
struct mdbcw_page __iomem *dbcw = mhu->mbx->dbcw;
for (i = 0; i < e->num_chans; i++) {
writel_relaxed(0xFFFFFFFF, &dbcw[i].clr);
writel_relaxed(0xFFFFFFFF, &dbcw[i].msk_set);
writel_relaxed_bitmask(0x1, &dbcw[i].ctrl, comb_en);
}
}
}
static int mhuv3_dbe_channels_init(struct mhuv3 *mhu)
{
struct mhuv3_extension *e = mhu->ext[DBE_EXT];
struct mbox_controller *mbox = &mhu->mbox;
struct mbox_chan *chans;
int i;
chans = mbox->chans + mbox->num_chans;
e->base_ch_idx = mbox->num_chans;
for (i = 0; i < e->num_chans; i++) {
struct mhuv3_mbox_chan_priv *priv;
int k;
for (k = 0; k < MHUV3_FLAG_BITS; k++) {
priv = devm_kmalloc(mbox->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->ch_idx = i;
priv->ops = &mhuv3_doorbell_ops;
priv->doorbell = k;
chans++->con_priv = priv;
mbox->num_chans++;
}
}
spin_lock_init(&e->pending_lock);
return 0;
}
static bool mhuv3_dbe_doorbell_lookup(struct mhuv3 *mhu, unsigned int channel,
unsigned int *db)
{
struct mhuv3_extension *e = mhu->ext[DBE_EXT];
struct device *dev = mhu->mbox.dev;
u32 st;
if (mhu->frame == PBX_FRAME) {
u32 active_dbs, fired_dbs;
st = readl_relaxed_bitmask(&mhu->pbx->dbcw[channel].int_st,
tfr_ack);
if (!st)
goto err_spurious;
active_dbs = readl_relaxed(&mhu->pbx->dbcw[channel].st);
scoped_guard(spinlock_irqsave, &e->pending_lock) {
fired_dbs = e->pending_db[channel] & ~active_dbs;
if (!fired_dbs)
goto err_spurious;
*db = __ffs(fired_dbs);
e->pending_db[channel] &= ~BIT(*db);
}
fired_dbs &= ~BIT(*db);
/* Clear TFR Ack if no more doorbells pending */
if (!fired_dbs)
writel_relaxed_bitmask(0x1,
&mhu->pbx->dbcw[channel].int_clr,
tfr_ack);
} else {
st = readl_relaxed(&mhu->mbx->dbcw[channel].st_msk);
if (!st)
goto err_spurious;
*db = __ffs(st);
}
return true;
err_spurious:
dev_warn(dev, "Spurious IRQ on %s channel:%d\n",
mhuv3_str[mhu->frame], channel);
return false;
}
static struct mbox_chan *mhuv3_dbe_chan_from_comb_irq_get(struct mhuv3 *mhu)
{
struct mhuv3_extension *e = mhu->ext[DBE_EXT];
struct device *dev = mhu->mbox.dev;
int i;
for (i = 0; i < MHUV3_DBCH_CMB_INT_ST_REG_CNT; i++) {
unsigned int channel, db;
u32 cmb_st;
cmb_st = readl_relaxed(&mhu->ctrl->dbch_int_st[i]);
if (!cmb_st)
continue;
channel = i * MHUV3_FLAG_BITS + __ffs(cmb_st);
if (channel >= e->num_chans) {
dev_err(dev, "Invalid %s channel:%d\n",
mhuv3_str[mhu->frame], channel);
return ERR_PTR(-EIO);
}
if (!mhuv3_dbe_doorbell_lookup(mhu, channel, &db))
continue;
dev_dbg(dev, "Found %s ch[%d]/db[%d]\n",
mhuv3_str[mhu->frame], channel, db);
return &mhu->mbox.chans[channel * MHUV3_FLAG_BITS + db];
}
return ERR_PTR(-EIO);
}
static int mhuv3_dbe_init(struct mhuv3 *mhu)
{
struct device *dev = mhu->mbox.dev;
struct mhuv3_extension *e;
if (!readl_relaxed_bitmask(&mhu->ctrl->feat_spt0, dbe_spt))
return 0;
dev_dbg(dev, "%s: Initializing DBE Extension.\n", mhuv3_str[mhu->frame]);
e = devm_kzalloc(dev, sizeof(*e), GFP_KERNEL);
if (!e)
return -ENOMEM;
e->type = DBE_EXT;
/* Note that, by the spec, the number of channels is (num_dbch + 1) */
e->num_chans =
readl_relaxed_bitmask(&mhu->ctrl->dbch_cfg0, num_dbch) + 1;
e->mbox_of_xlate = mhuv3_dbe_mbox_of_xlate;
e->combined_irq_setup = mhuv3_dbe_combined_irq_setup;
e->channels_init = mhuv3_dbe_channels_init;
e->chan_from_comb_irq_get = mhuv3_dbe_chan_from_comb_irq_get;
mhu->num_chans += e->num_chans * MHUV3_FLAG_BITS;
mhu->ext[DBE_EXT] = e;
dev_dbg(dev, "%s: found %d DBE channels.\n",
mhuv3_str[mhu->frame], e->num_chans);
return 0;
}
static int mhuv3_fce_init(struct mhuv3 *mhu)
{
struct device *dev = mhu->mbox.dev;
if (!readl_relaxed_bitmask(&mhu->ctrl->feat_spt0, fce_spt))
return 0;
dev_dbg(dev, "%s: FCE Extension not supported by driver.\n",
mhuv3_str[mhu->frame]);
return 0;
}
static int mhuv3_fe_init(struct mhuv3 *mhu)
{
struct device *dev = mhu->mbox.dev;
if (!readl_relaxed_bitmask(&mhu->ctrl->feat_spt0, fe_spt))
return 0;
dev_dbg(dev, "%s: FE Extension not supported by driver.\n",
mhuv3_str[mhu->frame]);
return 0;
}
static mhuv3_extension_initializer mhuv3_extension_init[NUM_EXT] = {
mhuv3_dbe_init,
mhuv3_fce_init,
mhuv3_fe_init,
};
static int mhuv3_initialize_channels(struct device *dev, struct mhuv3 *mhu)
{
struct mbox_controller *mbox = &mhu->mbox;
int i, ret = 0;
mbox->chans = devm_kcalloc(dev, mhu->num_chans,
sizeof(*mbox->chans), GFP_KERNEL);
if (!mbox->chans)
return dev_err_probe(dev, -ENOMEM,
"Failed to initialize channels\n");
for (i = 0; i < NUM_EXT && !ret; i++)
if (mhu->ext[i])
ret = mhu->ext[i]->channels_init(mhu);
return ret;
}
static struct mbox_chan *mhuv3_mbox_of_xlate(struct mbox_controller *mbox,
const struct of_phandle_args *pa)
{
struct mhuv3 *mhu = mhu_from_mbox(mbox);
unsigned int type, channel, param;
if (pa->args_count != MHUV3_MBOX_CELLS)
return ERR_PTR(-EINVAL);
type = pa->args[MHUV3_MBOX_CELL_TYPE];
if (type >= NUM_EXT)
return ERR_PTR(-EINVAL);
channel = pa->args[MHUV3_MBOX_CELL_CHWN];
param = pa->args[MHUV3_MBOX_CELL_PARAM];
return mhu->ext[type]->mbox_of_xlate(mhu, channel, param);
}
static void mhu_frame_cleanup_actions(void *data)
{
struct mhuv3 *mhu = data;
writel_relaxed_bitmask(0x0, &mhu->ctrl->x_ctrl, op_req);
}
static int mhuv3_frame_init(struct mhuv3 *mhu, void __iomem *regs)
{
struct device *dev = mhu->mbox.dev;
int i;
mhu->ctrl = regs;
mhu->frame = readl_relaxed_bitmask(&mhu->ctrl->blk_id, id);
if (mhu->frame > MBX_FRAME)
return dev_err_probe(dev, -EINVAL,
"Invalid Frame type- %d\n", mhu->frame);
mhu->major = readl_relaxed_bitmask(&mhu->ctrl->aidr, arch_major_rev);
mhu->minor = readl_relaxed_bitmask(&mhu->ctrl->aidr, arch_minor_rev);
mhu->implem = readl_relaxed_bitmask(&mhu->ctrl->iidr, implementer);
mhu->rev = readl_relaxed_bitmask(&mhu->ctrl->iidr, revision);
mhu->var = readl_relaxed_bitmask(&mhu->ctrl->iidr, variant);
mhu->prod_id = readl_relaxed_bitmask(&mhu->ctrl->iidr, product_id);
if (mhu->major != MHUV3_MAJOR_VERSION)
return dev_err_probe(dev, -EINVAL,
"Unsupported MHU %s block - major:%d minor:%d\n",
mhuv3_str[mhu->frame], mhu->major,
mhu->minor);
mhu->auto_op_full =
!!readl_relaxed_bitmask(&mhu->ctrl->feat_spt1, auto_op_spt);
/* Request the PBX/MBX to remain operational */
if (mhu->auto_op_full) {
writel_relaxed_bitmask(0x1, &mhu->ctrl->x_ctrl, op_req);
devm_add_action_or_reset(dev, mhu_frame_cleanup_actions, mhu);
}
dev_dbg(dev,
"Found MHU %s block - major:%d minor:%d\n implem:0x%X rev:0x%X var:0x%X prod_id:0x%X",
mhuv3_str[mhu->frame], mhu->major, mhu->minor,
mhu->implem, mhu->rev, mhu->var, mhu->prod_id);
if (mhu->frame == PBX_FRAME)
mhu->pbx = regs;
else
mhu->mbx = regs;
for (i = 0; i < NUM_EXT; i++) {
int ret;
/*
* Note that extensions initialization fails only when such
* extension initialization routine fails and the extensions
* was found to be supported in hardware and in software.
*/
ret = mhuv3_extension_init[i](mhu);
if (ret)
return dev_err_probe(dev, ret,
"Failed to initialize %s %s\n",
mhuv3_str[mhu->frame],
mhuv3_ext_str[i]);
}
return 0;
}
static irqreturn_t mhuv3_pbx_comb_interrupt(int irq, void *arg)
{
unsigned int i, found = 0;
struct mhuv3 *mhu = arg;
struct mbox_chan *chan;
struct device *dev;
int ret = IRQ_NONE;
dev = mhu->mbox.dev;
for (i = 0; i < NUM_EXT; i++) {
struct mhuv3_mbox_chan_priv *priv;
/* FCE does not participate to the PBX combined */
if (i == FCE_EXT || !mhu->ext[i])
continue;
chan = mhu->ext[i]->chan_from_comb_irq_get(mhu);
if (IS_ERR(chan))
continue;
found++;
priv = chan->con_priv;
if (!chan->cl) {
dev_warn(dev, "TX Ack on UNBOUND channel (%u)\n",
priv->ch_idx);
continue;
}
mbox_chan_txdone(chan, 0);
ret = IRQ_HANDLED;
}
if (found == 0)
dev_warn_once(dev, "Failed to find channel for the TX interrupt\n");
return ret;
}
static irqreturn_t mhuv3_mbx_comb_interrupt(int irq, void *arg)
{
unsigned int i, found = 0;
struct mhuv3 *mhu = arg;
struct mbox_chan *chan;
struct device *dev;
int ret = IRQ_NONE;
dev = mhu->mbox.dev;
for (i = 0; i < NUM_EXT; i++) {
struct mhuv3_mbox_chan_priv *priv;
void *data __free(kfree) = NULL;
if (!mhu->ext[i])
continue;
/* Process any extension which could be source of the IRQ */
chan = mhu->ext[i]->chan_from_comb_irq_get(mhu);
if (IS_ERR(chan))
continue;
found++;
/* From here on we need to call rx_complete even on error */
priv = chan->con_priv;
if (!chan->cl) {
dev_warn(dev, "RX Data on UNBOUND channel (%u)\n",
priv->ch_idx);
goto rx_ack;
}
/* Read optional in-band LE data first. */
if (priv->ops->read_data) {
data = priv->ops->read_data(mhu, chan);
if (IS_ERR(data)) {
dev_err(dev,
"Failed to read in-band data. err:%ld\n",
PTR_ERR(no_free_ptr(data)));
goto rx_ack;
}
}
mbox_chan_received_data(chan, data);
ret = IRQ_HANDLED;
/*
* Acknowledge transfer after any possible optional
* out-of-band data has also been retrieved via
* mbox_chan_received_data().
*/
rx_ack:
if (priv->ops->rx_complete)
priv->ops->rx_complete(mhu, chan);
}
if (found == 0)
dev_warn_once(dev, "Failed to find channel for the RX interrupt\n");
return ret;
}
static int mhuv3_setup_pbx(struct mhuv3 *mhu)
{
struct device *dev = mhu->mbox.dev;
mhu->mbox.ops = &mhuv3_sender_ops;
if (mhu->cmb_irq > 0) {
int ret, i;
ret = devm_request_threaded_irq(dev, mhu->cmb_irq, NULL,
mhuv3_pbx_comb_interrupt,
IRQF_ONESHOT, "mhuv3-pbx", mhu);
if (ret)
return dev_err_probe(dev, ret,
"Failed to request PBX IRQ\n");
mhu->mbox.txdone_irq = true;
mhu->mbox.txdone_poll = false;
for (i = 0; i < NUM_EXT; i++)
if (mhu->ext[i])
mhu->ext[i]->combined_irq_setup(mhu);
dev_dbg(dev, "MHUv3 PBX IRQs initialized.\n");
return 0;
}
dev_info(dev, "Using PBX in Tx polling mode.\n");
mhu->mbox.txdone_irq = false;
mhu->mbox.txdone_poll = true;
mhu->mbox.txpoll_period = 1;
return 0;
}
static int mhuv3_setup_mbx(struct mhuv3 *mhu)
{
struct device *dev = mhu->mbox.dev;
int ret, i;
mhu->mbox.ops = &mhuv3_receiver_ops;
if (mhu->cmb_irq <= 0)
return dev_err_probe(dev, -EINVAL,
"MBX combined IRQ is missing !\n");
ret = devm_request_threaded_irq(dev, mhu->cmb_irq, NULL,
mhuv3_mbx_comb_interrupt, IRQF_ONESHOT,
"mhuv3-mbx", mhu);
if (ret)
return dev_err_probe(dev, ret, "Failed to request MBX IRQ\n");
for (i = 0; i < NUM_EXT; i++)
if (mhu->ext[i])
mhu->ext[i]->combined_irq_setup(mhu);
dev_dbg(dev, "MHUv3 MBX IRQs initialized.\n");
return ret;
}
static int mhuv3_irqs_init(struct mhuv3 *mhu, struct platform_device *pdev)
{
dev_dbg(mhu->mbox.dev, "Initializing %s block.\n",
mhuv3_str[mhu->frame]);
if (mhu->frame == PBX_FRAME) {
mhu->cmb_irq =
platform_get_irq_byname_optional(pdev, "combined");
return mhuv3_setup_pbx(mhu);
}
mhu->cmb_irq = platform_get_irq_byname(pdev, "combined");
return mhuv3_setup_mbx(mhu);
}
static int mhuv3_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
void __iomem *regs;
struct mhuv3 *mhu;
int ret;
mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL);
if (!mhu)
return -ENOMEM;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
mhu->mbox.dev = dev;
ret = mhuv3_frame_init(mhu, regs);
if (ret)
return ret;
ret = mhuv3_irqs_init(mhu, pdev);
if (ret)
return ret;
mhu->mbox.of_xlate = mhuv3_mbox_of_xlate;
ret = mhuv3_initialize_channels(dev, mhu);
if (ret)
return ret;
ret = devm_mbox_controller_register(dev, &mhu->mbox);
if (ret)
return dev_err_probe(dev, ret,
"Failed to register ARM MHUv3 driver\n");
return ret;
}
static const struct of_device_id mhuv3_of_match[] = {
{ .compatible = "arm,mhuv3", .data = NULL },
{}
};
MODULE_DEVICE_TABLE(of, mhuv3_of_match);
static struct platform_driver mhuv3_driver = {
.driver = {
.name = "arm-mhuv3-mailbox",
.of_match_table = mhuv3_of_match,
},
.probe = mhuv3_probe,
};
module_platform_driver(mhuv3_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ARM MHUv3 Driver");
MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.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