Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Cristian Marussi | 4896 | 100.00% | 1 | 100.00% |
Total | 4896 | 1 |
// 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>");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1