// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2018, NVIDIA CORPORATION. */ #include #include #include #include #include #include #include "bpmp-private.h" #define TRIGGER_OFFSET 0x000 #define RESULT_OFFSET(id) (0xc00 + id * 4) #define TRIGGER_ID_SHIFT 16 #define TRIGGER_CMD_GET 4 #define STA_OFFSET 0 #define SET_OFFSET 4 #define CLR_OFFSET 8 #define CH_MASK(ch) (0x3 << ((ch) * 2)) #define SL_SIGL(ch) (0x0 << ((ch) * 2)) #define SL_QUED(ch) (0x1 << ((ch) * 2)) #define MA_FREE(ch) (0x2 << ((ch) * 2)) #define MA_ACKD(ch) (0x3 << ((ch) * 2)) struct tegra210_bpmp { void __iomem *atomics; void __iomem *arb_sema; struct irq_data *tx_irq_data; }; static u32 bpmp_channel_status(struct tegra_bpmp *bpmp, unsigned int index) { struct tegra210_bpmp *priv = bpmp->priv; return __raw_readl(priv->arb_sema + STA_OFFSET) & CH_MASK(index); } static bool tegra210_bpmp_is_response_ready(struct tegra_bpmp_channel *channel) { unsigned int index = channel->index; return bpmp_channel_status(channel->bpmp, index) == MA_ACKD(index); } static bool tegra210_bpmp_is_request_ready(struct tegra_bpmp_channel *channel) { unsigned int index = channel->index; return bpmp_channel_status(channel->bpmp, index) == SL_SIGL(index); } static bool tegra210_bpmp_is_request_channel_free(struct tegra_bpmp_channel *channel) { unsigned int index = channel->index; return bpmp_channel_status(channel->bpmp, index) == MA_FREE(index); } static bool tegra210_bpmp_is_response_channel_free(struct tegra_bpmp_channel *channel) { unsigned int index = channel->index; return bpmp_channel_status(channel->bpmp, index) == SL_QUED(index); } static int tegra210_bpmp_post_request(struct tegra_bpmp_channel *channel) { struct tegra210_bpmp *priv = channel->bpmp->priv; __raw_writel(CH_MASK(channel->index), priv->arb_sema + CLR_OFFSET); return 0; } static int tegra210_bpmp_post_response(struct tegra_bpmp_channel *channel) { struct tegra210_bpmp *priv = channel->bpmp->priv; __raw_writel(MA_ACKD(channel->index), priv->arb_sema + SET_OFFSET); return 0; } static int tegra210_bpmp_ack_response(struct tegra_bpmp_channel *channel) { struct tegra210_bpmp *priv = channel->bpmp->priv; __raw_writel(MA_ACKD(channel->index) ^ MA_FREE(channel->index), priv->arb_sema + CLR_OFFSET); return 0; } static int tegra210_bpmp_ack_request(struct tegra_bpmp_channel *channel) { struct tegra210_bpmp *priv = channel->bpmp->priv; __raw_writel(SL_QUED(channel->index), priv->arb_sema + SET_OFFSET); return 0; } static int tegra210_bpmp_ring_doorbell(struct tegra_bpmp *bpmp) { struct tegra210_bpmp *priv = bpmp->priv; struct irq_data *irq_data = priv->tx_irq_data; /* * Tegra Legacy Interrupt Controller (LIC) is used to notify BPMP of * available messages */ if (irq_data->chip->irq_retrigger) return irq_data->chip->irq_retrigger(irq_data); return -EINVAL; } static irqreturn_t rx_irq(int irq, void *data) { struct tegra_bpmp *bpmp = data; tegra_bpmp_handle_rx(bpmp); return IRQ_HANDLED; } static int tegra210_bpmp_channel_init(struct tegra_bpmp_channel *channel, struct tegra_bpmp *bpmp, unsigned int index) { struct tegra210_bpmp *priv = bpmp->priv; u32 address; void *p; /* Retrieve channel base address from BPMP */ writel(index << TRIGGER_ID_SHIFT | TRIGGER_CMD_GET, priv->atomics + TRIGGER_OFFSET); address = readl(priv->atomics + RESULT_OFFSET(index)); p = devm_ioremap(bpmp->dev, address, 0x80); if (!p) return -ENOMEM; channel->ib = p; channel->ob = p; channel->index = index; init_completion(&channel->completion); channel->bpmp = bpmp; return 0; } static int tegra210_bpmp_init(struct tegra_bpmp *bpmp) { struct platform_device *pdev = to_platform_device(bpmp->dev); struct tegra210_bpmp *priv; struct resource *res; unsigned int i; int err; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; bpmp->priv = priv; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->atomics = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->atomics)) return PTR_ERR(priv->atomics); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); priv->arb_sema = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->arb_sema)) return PTR_ERR(priv->arb_sema); err = tegra210_bpmp_channel_init(bpmp->tx_channel, bpmp, bpmp->soc->channels.cpu_tx.offset); if (err < 0) return err; err = tegra210_bpmp_channel_init(bpmp->rx_channel, bpmp, bpmp->soc->channels.cpu_rx.offset); if (err < 0) return err; for (i = 0; i < bpmp->threaded.count; i++) { unsigned int index = bpmp->soc->channels.thread.offset + i; err = tegra210_bpmp_channel_init(&bpmp->threaded_channels[i], bpmp, index); if (err < 0) return err; } err = platform_get_irq_byname(pdev, "tx"); if (err < 0) { dev_err(&pdev->dev, "failed to get TX IRQ: %d\n", err); return err; } priv->tx_irq_data = irq_get_irq_data(err); if (!priv->tx_irq_data) { dev_err(&pdev->dev, "failed to get IRQ data for TX IRQ\n"); return err; } err = platform_get_irq_byname(pdev, "rx"); if (err < 0) { dev_err(&pdev->dev, "failed to get rx IRQ: %d\n", err); return err; } err = devm_request_irq(&pdev->dev, err, rx_irq, IRQF_NO_SUSPEND, dev_name(&pdev->dev), bpmp); if (err < 0) { dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); return err; } return 0; } const struct tegra_bpmp_ops tegra210_bpmp_ops = { .init = tegra210_bpmp_init, .is_response_ready = tegra210_bpmp_is_response_ready, .is_request_ready = tegra210_bpmp_is_request_ready, .ack_response = tegra210_bpmp_ack_response, .ack_request = tegra210_bpmp_ack_request, .is_response_channel_free = tegra210_bpmp_is_response_channel_free, .is_request_channel_free = tegra210_bpmp_is_request_channel_free, .post_response = tegra210_bpmp_post_response, .post_request = tegra210_bpmp_post_request, .ring_doorbell = tegra210_bpmp_ring_doorbell, };