diff options
Diffstat (limited to 'drivers/mailbox/tegra-hsp.c')
| -rw-r--r-- | drivers/mailbox/tegra-hsp.c | 253 |
1 files changed, 203 insertions, 50 deletions
diff --git a/drivers/mailbox/tegra-hsp.c b/drivers/mailbox/tegra-hsp.c index acd0675da681..ed9a0bb2bcd8 100644 --- a/drivers/mailbox/tegra-hsp.c +++ b/drivers/mailbox/tegra-hsp.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2018, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2016-2025, NVIDIA CORPORATION. All rights reserved. */ #include <linux/delay.h> @@ -8,7 +8,6 @@ #include <linux/io.h> #include <linux/mailbox_controller.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm.h> #include <linux/slab.h> @@ -29,12 +28,6 @@ #define HSP_INT_FULL_MASK 0xff #define HSP_INT_DIMENSIONING 0x380 -#define HSP_nSM_SHIFT 0 -#define HSP_nSS_SHIFT 4 -#define HSP_nAS_SHIFT 8 -#define HSP_nDB_SHIFT 12 -#define HSP_nSI_SHIFT 16 -#define HSP_nINT_MASK 0xf #define HSP_DB_TRIGGER 0x0 #define HSP_DB_ENABLE 0x4 @@ -46,10 +39,18 @@ #define HSP_SM_SHRD_MBOX_FULL_INT_IE 0x04 #define HSP_SM_SHRD_MBOX_EMPTY_INT_IE 0x08 +#define HSP_SHRD_MBOX_TYPE1_TAG 0x40 +#define HSP_SHRD_MBOX_TYPE1_DATA0 0x48 +#define HSP_SHRD_MBOX_TYPE1_DATA1 0x4c +#define HSP_SHRD_MBOX_TYPE1_DATA2 0x50 +#define HSP_SHRD_MBOX_TYPE1_DATA3 0x54 + #define HSP_DB_CCPLEX 1 #define HSP_DB_BPMP 3 #define HSP_DB_MAX 7 +#define HSP_MBOX_TYPE_MASK 0xff + struct tegra_hsp_channel; struct tegra_hsp; @@ -67,8 +68,14 @@ struct tegra_hsp_doorbell { unsigned int index; }; +struct tegra_hsp_sm_ops { + void (*send)(struct tegra_hsp_channel *channel, void *data); + void (*recv)(struct tegra_hsp_channel *channel); +}; + struct tegra_hsp_mailbox { struct tegra_hsp_channel channel; + const struct tegra_hsp_sm_ops *ops; unsigned int index; bool producer; }; @@ -82,6 +89,22 @@ struct tegra_hsp_db_map { struct tegra_hsp_soc { const struct tegra_hsp_db_map *map; bool has_per_mb_ie; + bool has_128_bit_mb; + unsigned int reg_stride; + + /* Shifts for dimensioning register. */ + unsigned int si_shift; + unsigned int db_shift; + unsigned int as_shift; + unsigned int ss_shift; + unsigned int sm_shift; + + /* Masks for dimensioning register. */ + unsigned int si_mask; + unsigned int db_mask; + unsigned int as_mask; + unsigned int ss_mask; + unsigned int sm_mask; }; struct tegra_hsp { @@ -208,8 +231,7 @@ static irqreturn_t tegra_hsp_shared_irq(int irq, void *data) { struct tegra_hsp *hsp = data; unsigned long bit, mask; - u32 status, value; - void *msg; + u32 status; status = tegra_hsp_readl(hsp, HSP_INT_IR) & hsp->mask; @@ -245,25 +267,8 @@ static irqreturn_t tegra_hsp_shared_irq(int irq, void *data) for_each_set_bit(bit, &mask, hsp->num_sm) { struct tegra_hsp_mailbox *mb = &hsp->mailboxes[bit]; - if (!mb->producer) { - value = tegra_hsp_channel_readl(&mb->channel, - HSP_SM_SHRD_MBOX); - value &= ~HSP_SM_SHRD_MBOX_FULL; - msg = (void *)(unsigned long)value; - mbox_chan_received_data(mb->channel.chan, msg); - - /* - * Need to clear all bits here since some producers, - * such as TCU, depend on fields in the register - * getting cleared by the consumer. - * - * The mailbox API doesn't give the consumers a way - * of doing that explicitly, so we have to make sure - * we cover all possible cases. - */ - tegra_hsp_channel_writel(&mb->channel, 0x0, - HSP_SM_SHRD_MBOX); - } + if (!mb->producer) + mb->ops->recv(&mb->channel); } return IRQ_HANDLED; @@ -282,7 +287,7 @@ tegra_hsp_doorbell_create(struct tegra_hsp *hsp, const char *name, return ERR_PTR(-ENOMEM); offset = (1 + (hsp->num_sm / 2) + hsp->num_ss + hsp->num_as) * SZ_64K; - offset += index * 0x100; + offset += index * hsp->soc->reg_stride; db->channel.regs = hsp->regs + offset; db->channel.hsp = hsp; @@ -372,21 +377,99 @@ static const struct mbox_chan_ops tegra_hsp_db_ops = { .shutdown = tegra_hsp_doorbell_shutdown, }; +static void tegra_hsp_sm_send32(struct tegra_hsp_channel *channel, void *data) +{ + u32 value; + + /* copy data and mark mailbox full */ + value = (u32)(unsigned long)data; + value |= HSP_SM_SHRD_MBOX_FULL; + + tegra_hsp_channel_writel(channel, value, HSP_SM_SHRD_MBOX); +} + +static void tegra_hsp_sm_recv32(struct tegra_hsp_channel *channel) +{ + u32 value; + void *msg; + + value = tegra_hsp_channel_readl(channel, HSP_SM_SHRD_MBOX); + value &= ~HSP_SM_SHRD_MBOX_FULL; + msg = (void *)(unsigned long)value; + + /* + * Need to clear all bits here since some producers, such as TCU, depend + * on fields in the register getting cleared by the consumer. + * + * The mailbox API doesn't give the consumers a way of doing that + * explicitly, so we have to make sure we cover all possible cases. + */ + tegra_hsp_channel_writel(channel, 0x0, HSP_SM_SHRD_MBOX); + + mbox_chan_received_data(channel->chan, msg); +} + +static const struct tegra_hsp_sm_ops tegra_hsp_sm_32bit_ops = { + .send = tegra_hsp_sm_send32, + .recv = tegra_hsp_sm_recv32, +}; + +static void tegra_hsp_sm_send128(struct tegra_hsp_channel *channel, void *data) +{ + u32 value[4]; + + memcpy(value, data, sizeof(value)); + + /* Copy data */ + tegra_hsp_channel_writel(channel, value[0], HSP_SHRD_MBOX_TYPE1_DATA0); + tegra_hsp_channel_writel(channel, value[1], HSP_SHRD_MBOX_TYPE1_DATA1); + tegra_hsp_channel_writel(channel, value[2], HSP_SHRD_MBOX_TYPE1_DATA2); + tegra_hsp_channel_writel(channel, value[3], HSP_SHRD_MBOX_TYPE1_DATA3); + + /* Update tag to mark mailbox full */ + tegra_hsp_channel_writel(channel, HSP_SM_SHRD_MBOX_FULL, + HSP_SHRD_MBOX_TYPE1_TAG); +} + +static void tegra_hsp_sm_recv128(struct tegra_hsp_channel *channel) +{ + u32 value[4]; + void *msg; + + value[0] = tegra_hsp_channel_readl(channel, HSP_SHRD_MBOX_TYPE1_DATA0); + value[1] = tegra_hsp_channel_readl(channel, HSP_SHRD_MBOX_TYPE1_DATA1); + value[2] = tegra_hsp_channel_readl(channel, HSP_SHRD_MBOX_TYPE1_DATA2); + value[3] = tegra_hsp_channel_readl(channel, HSP_SHRD_MBOX_TYPE1_DATA3); + + msg = (void *)(unsigned long)value; + + /* + * Clear data registers and tag. + */ + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_DATA0); + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_DATA1); + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_DATA2); + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_DATA3); + tegra_hsp_channel_writel(channel, 0x0, HSP_SHRD_MBOX_TYPE1_TAG); + + mbox_chan_received_data(channel->chan, msg); +} + +static const struct tegra_hsp_sm_ops tegra_hsp_sm_128bit_ops = { + .send = tegra_hsp_sm_send128, + .recv = tegra_hsp_sm_recv128, +}; + static int tegra_hsp_mailbox_send_data(struct mbox_chan *chan, void *data) { struct tegra_hsp_mailbox *mb = chan->con_priv; struct tegra_hsp *hsp = mb->channel.hsp; unsigned long flags; - u32 value; if (WARN_ON(!mb->producer)) return -EPERM; - /* copy data and mark mailbox full */ - value = (u32)(unsigned long)data; - value |= HSP_SM_SHRD_MBOX_FULL; - - tegra_hsp_channel_writel(&mb->channel, value, HSP_SM_SHRD_MBOX); + mb->ops->send(&mb->channel, data); /* enable EMPTY interrupt for the shared mailbox */ spin_lock_irqsave(&hsp->lock, flags); @@ -412,6 +495,11 @@ static int tegra_hsp_mailbox_flush(struct mbox_chan *chan, value = tegra_hsp_channel_readl(ch, HSP_SM_SHRD_MBOX); if ((value & HSP_SM_SHRD_MBOX_FULL) == 0) { mbox_chan_txdone(chan, 0); + + /* Wait until channel is empty */ + if (chan->active_req != NULL) + continue; + return 0; } @@ -547,12 +635,21 @@ static struct mbox_chan *tegra_hsp_sm_xlate(struct mbox_controller *mbox, index = args->args[1] & TEGRA_HSP_SM_MASK; - if (type != TEGRA_HSP_MBOX_TYPE_SM || !hsp->shared_irqs || - index >= hsp->num_sm) + if ((type & HSP_MBOX_TYPE_MASK) != TEGRA_HSP_MBOX_TYPE_SM || + !hsp->shared_irqs || index >= hsp->num_sm) return ERR_PTR(-ENODEV); mb = &hsp->mailboxes[index]; + if (type & TEGRA_HSP_MBOX_TYPE_SM_128BIT) { + if (!hsp->soc->has_128_bit_mb) + return ERR_PTR(-ENODEV); + + mb->ops = &tegra_hsp_sm_128bit_ops; + } else { + mb->ops = &tegra_hsp_sm_32bit_ops; + } + if ((args->args[1] & TEGRA_HSP_SM_FLAG_TX) == 0) mb->producer = false; else @@ -640,7 +737,6 @@ static int tegra_hsp_request_shared_irq(struct tegra_hsp *hsp) static int tegra_hsp_probe(struct platform_device *pdev) { struct tegra_hsp *hsp; - struct resource *res; unsigned int i; u32 value; int err; @@ -654,17 +750,16 @@ static int tegra_hsp_probe(struct platform_device *pdev) INIT_LIST_HEAD(&hsp->doorbells); spin_lock_init(&hsp->lock); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - hsp->regs = devm_ioremap_resource(&pdev->dev, res); + hsp->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(hsp->regs)) return PTR_ERR(hsp->regs); value = tegra_hsp_readl(hsp, HSP_INT_DIMENSIONING); - hsp->num_sm = (value >> HSP_nSM_SHIFT) & HSP_nINT_MASK; - hsp->num_ss = (value >> HSP_nSS_SHIFT) & HSP_nINT_MASK; - hsp->num_as = (value >> HSP_nAS_SHIFT) & HSP_nINT_MASK; - hsp->num_db = (value >> HSP_nDB_SHIFT) & HSP_nINT_MASK; - hsp->num_si = (value >> HSP_nSI_SHIFT) & HSP_nINT_MASK; + hsp->num_sm = (value >> hsp->soc->sm_shift) & hsp->soc->sm_mask; + hsp->num_ss = (value >> hsp->soc->ss_shift) & hsp->soc->ss_mask; + hsp->num_as = (value >> hsp->soc->as_shift) & hsp->soc->as_mask; + hsp->num_db = (value >> hsp->soc->db_shift) & hsp->soc->db_mask; + hsp->num_si = (value >> hsp->soc->si_shift) & hsp->soc->si_mask; err = platform_get_irq_byname_optional(pdev, "doorbell"); if (err >= 0) @@ -783,13 +878,11 @@ static int tegra_hsp_probe(struct platform_device *pdev) return 0; } -static int tegra_hsp_remove(struct platform_device *pdev) +static void tegra_hsp_remove(struct platform_device *pdev) { struct tegra_hsp *hsp = platform_get_drvdata(pdev); lockdep_unregister_key(&hsp->lock_key); - - return 0; } static int __maybe_unused tegra_hsp_resume(struct device *dev) @@ -799,7 +892,7 @@ static int __maybe_unused tegra_hsp_resume(struct device *dev) struct tegra_hsp_doorbell *db; list_for_each_entry(db, &hsp->doorbells, list) { - if (db && db->channel.chan) + if (db->channel.chan) tegra_hsp_doorbell_startup(db->channel.chan); } @@ -828,16 +921,76 @@ static const struct tegra_hsp_db_map tegra186_hsp_db_map[] = { static const struct tegra_hsp_soc tegra186_hsp_soc = { .map = tegra186_hsp_db_map, .has_per_mb_ie = false, + .has_128_bit_mb = false, + .reg_stride = 0x100, + .si_shift = 16, + .db_shift = 12, + .as_shift = 8, + .ss_shift = 4, + .sm_shift = 0, + .si_mask = 0xf, + .db_mask = 0xf, + .as_mask = 0xf, + .ss_mask = 0xf, + .sm_mask = 0xf, }; static const struct tegra_hsp_soc tegra194_hsp_soc = { .map = tegra186_hsp_db_map, .has_per_mb_ie = true, + .has_128_bit_mb = false, + .reg_stride = 0x100, + .si_shift = 16, + .db_shift = 12, + .as_shift = 8, + .ss_shift = 4, + .sm_shift = 0, + .si_mask = 0xf, + .db_mask = 0xf, + .as_mask = 0xf, + .ss_mask = 0xf, + .sm_mask = 0xf, +}; + +static const struct tegra_hsp_soc tegra234_hsp_soc = { + .map = tegra186_hsp_db_map, + .has_per_mb_ie = false, + .has_128_bit_mb = true, + .reg_stride = 0x100, + .si_shift = 16, + .db_shift = 12, + .as_shift = 8, + .ss_shift = 4, + .sm_shift = 0, + .si_mask = 0xf, + .db_mask = 0xf, + .as_mask = 0xf, + .ss_mask = 0xf, + .sm_mask = 0xf, +}; + +static const struct tegra_hsp_soc tegra264_hsp_soc = { + .map = tegra186_hsp_db_map, + .has_per_mb_ie = false, + .has_128_bit_mb = true, + .reg_stride = 0x1000, + .si_shift = 17, + .db_shift = 12, + .as_shift = 8, + .ss_shift = 4, + .sm_shift = 0, + .si_mask = 0x1f, + .db_mask = 0x1f, + .as_mask = 0xf, + .ss_mask = 0xf, + .sm_mask = 0xf, }; static const struct of_device_id tegra_hsp_match[] = { { .compatible = "nvidia,tegra186-hsp", .data = &tegra186_hsp_soc }, { .compatible = "nvidia,tegra194-hsp", .data = &tegra194_hsp_soc }, + { .compatible = "nvidia,tegra234-hsp", .data = &tegra234_hsp_soc }, + { .compatible = "nvidia,tegra264-hsp", .data = &tegra264_hsp_soc }, { } }; |
