From 986848ef501b28ccbe48336dfa201ad54b0e1e34 Mon Sep 17 00:00:00 2001 From: Allen Martin Date: Wed, 11 Jan 2023 18:14:53 -0800 Subject: [PATCH 11/15] WIP: add BFLB MBOX interrupt controller driver --- .../boot/dts/bouffalolab/bl808-sipeed-m1s.dts | 4 + arch/riscv/boot/dts/bouffalolab/bl808.dtsi | 14 + drivers/mailbox/Kconfig | 9 + drivers/mailbox/Makefile | 2 + drivers/mailbox/bflb-ipc.c | 381 ++++++++++++++++++ include/dt-bindings/mailbox/bflb-ipc.h | 16 + 6 files changed, 426 insertions(+) create mode 100644 drivers/mailbox/bflb-ipc.c create mode 100644 include/dt-bindings/mailbox/bflb-ipc.h diff --git a/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dts b/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dts index bdb502ea5a54..70259bad7dfd 100644 --- a/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dts +++ b/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dts @@ -45,3 +45,7 @@ rootfs@0 { &uart0 { status = "okay"; }; + +&ipclic { + status = "okay"; +}; diff --git a/arch/riscv/boot/dts/bouffalolab/bl808.dtsi b/arch/riscv/boot/dts/bouffalolab/bl808.dtsi index c98ebb14ee10..c5cda8d74ccd 100644 --- a/arch/riscv/boot/dts/bouffalolab/bl808.dtsi +++ b/arch/riscv/boot/dts/bouffalolab/bl808.dtsi @@ -4,6 +4,7 @@ */ #include +#include / { compatible = "bouffalolab,bl808"; @@ -60,6 +61,19 @@ uart0: serial@30002000 { status = "disabled"; }; + ipclic: mailbox@30005000 { + compatible = "bouffalolab,bflb-ipc"; + reg = <0x30005000 0x20>, + <0x30005020 0x20>, + <0x2000a800 0x20>, + <0x2000a820 0x20>; + interrupts = <54 IRQ_TYPE_LEVEL_HIGH>; + interrupt-controller; + #interrupt-cells = <3>; + #mbox-cells = <2>; + status = "disabled"; + }; + plic: interrupt-controller@e0000000 { compatible = "thead,c900-plic"; reg = <0xe0000000 0x4000000>; diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 1495965bc394..c215da153fd7 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -295,4 +295,13 @@ config QCOM_IPCC acts as an interrupt controller for receiving interrupts from clients. Say Y here if you want to build this driver. +config BFLB_IPC + tristate "Bouffalo Lab IPC driver" + depends on OF + help + (IPC) driver for BL808 devices. The driver provides mailbox support for + sending interrupts to the clients. On the other hand, the driver also + acts as an interrupt controller for receiving interrupts from clients. + Say Y here if you want to build this driver. + endif diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index fc9376117111..02bba5d03d4b 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -62,3 +62,5 @@ obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o + +obj-$(CONFIG_BFLB_IPC) += bflb-ipc.o diff --git a/drivers/mailbox/bflb-ipc.c b/drivers/mailbox/bflb-ipc.c new file mode 100644 index 000000000000..692dd20e35a8 --- /dev/null +++ b/drivers/mailbox/bflb-ipc.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Allen Martin + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* IPC Register offsets */ +#define IPC_REG_ISWR 0x00 /* Interrupt Set Write Register */ +#define IPC_REG_IRSRR 0x04 /* Interrupt raw status Register */ +#define IPC_REG_ICR 0x08 /* Interrupt Clear Register */ +#define IPC_REG_IUSR 0x0c /* Interrupt Unmask Set Register */ +#define IPC_REG_IUCR 0x10 /* Interrupt Unmask Clear Register */ +#define IPC_REG_ILSLR 0x14 /* Interrupt Line Sel Low Register */ +#define IPC_REG_ILSHR 0x18 /* Interrupt Line Sel High Register */ +#define IPC_REG_ISR 0x1c /* Interrupt status Register */ + +/** + * struct bflb_ipc_chan_info - Per-mailbox-channel info + * @client_id: The client-id to which the interrupt has to be triggered + * @signal_id: The signal-id to which the interrupt has to be triggered + */ +struct bflb_ipc_chan_info { + u16 client_id; + u16 signal_id; +}; + +/** + * struct bflb_ipc - Holder for the mailbox driver + * @dev: Device associated with this instance + * @base: Base address of each IPC frame (LP, M0) + * @irq_domain: The irq_domain associated with this instance + * @chans: The mailbox channels array + * @mchan: The per-mailbox channel info array + * @mbox: The mailbox controller + * @num_chans: Number of @chans elements + * @irq: Summary irq + */ +struct bflb_ipc { + struct device *dev; + void __iomem *base[4]; + struct irq_domain *irq_domain; + struct mbox_chan *chans; + struct bflb_ipc_chan_info *mchan; + struct mbox_controller mbox; + int num_chans; + int irq; +}; + +static inline struct bflb_ipc *to_bflb_ipc(struct mbox_controller *mbox) +{ + return container_of(mbox, struct bflb_ipc, mbox); +} + +static inline u32 bflb_ipc_get_hwirq(u16 source, u16 device) +{ + pr_debug("%s: source: %u, device: %u\n", __func__, source, device); + + return device; +} + +#if 0 +static void bflb_ipc_dump_regs(struct bflb_ipc *ipc) +{ + int i; + for (i=0; i<4; i++) { + dev_dbg(ipc->dev, "base %px\n", ipc->base[i]); + dev_dbg(ipc->dev, "ISWR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ISWR)); + dev_dbg(ipc->dev, "IRSRR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_IRSRR)); + dev_dbg(ipc->dev, "ICR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ICR)); + dev_dbg(ipc->dev, "IUSR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_IUSR)); + dev_dbg(ipc->dev, "IUCR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_IUCR)); + dev_dbg(ipc->dev, "ILSLR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ILSLR)); + dev_dbg(ipc->dev, "ILSHR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ILSHR)); + dev_dbg(ipc->dev, "ISR: 0x%08x\n", readl(ipc->base[i] + IPC_REG_ISR)); + } +} +#endif + +static irqreturn_t bflb_ipc_irq_fn(int irq, void *data) +{ + struct bflb_ipc *ipc = data; + unsigned long stat; + int pos; + + stat = readl(ipc->base[1] + IPC_REG_ISR); + for_each_set_bit(pos, &stat, 32) + generic_handle_domain_irq(ipc->irq_domain, pos); + writel(stat, ipc->base[1] + IPC_REG_ICR); + + /* EOI the irqs */ + writel(stat, ipc->base[2] + IPC_REG_ISWR); + + return IRQ_HANDLED; +} + +static void bflb_ipc_mask_irq(struct irq_data *irqd) +{ + struct bflb_ipc *ipc = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t hwirq = irqd_to_hwirq(irqd); + + writel(BIT(hwirq), ipc->base[1] + IPC_REG_IUCR); +} + +static void bflb_ipc_unmask_irq(struct irq_data *irqd) +{ + struct bflb_ipc *ipc = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t hwirq = irqd_to_hwirq(irqd); + + writel(BIT(hwirq), ipc->base[1] + IPC_REG_IUSR); +} + +static struct irq_chip bflb_ipc_irq_chip = { + .name = "BFLB MBOXIC", + .irq_mask = bflb_ipc_mask_irq, + .irq_unmask = bflb_ipc_unmask_irq, + .flags = IRQCHIP_SKIP_SET_WAKE, +}; + +static int bflb_ipc_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + struct bflb_ipc *ipc = d->host_data; + + irq_set_chip_and_handler(irq, &bflb_ipc_irq_chip, handle_level_irq); + irq_set_chip_data(irq, ipc); + irq_set_noprobe(irq); + + return 0; +} + +static int bflb_ipc_domain_xlate(struct irq_domain *d, + struct device_node *node, const u32 *intspec, + unsigned int intsize, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (intsize != 3) + return -EINVAL; + + *out_hwirq = bflb_ipc_get_hwirq(intspec[0], intspec[1]); + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static const struct irq_domain_ops bflb_ipc_irq_ops = { + .map = bflb_ipc_domain_map, + .xlate = bflb_ipc_domain_xlate, +}; + +static int bflb_ipc_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct bflb_ipc *ipc = to_bflb_ipc(chan->mbox); + struct bflb_ipc_chan_info *mchan = chan->con_priv; + u32 hwirq; + + hwirq = bflb_ipc_get_hwirq(mchan->client_id, mchan->signal_id); + + dev_dbg(ipc->dev, "%s: hwirq: %u\n", __func__, hwirq); + +// writel(hwirq, ipc->base + IPC_REG_SEND_ID); + + return 0; +} + +static void bflb_ipc_mbox_shutdown(struct mbox_chan *chan) +{ + pr_debug("%s\n", __func__); + chan->con_priv = NULL; +} + +static struct mbox_chan *bflb_ipc_mbox_xlate(struct mbox_controller *mbox, + const struct of_phandle_args *ph) +{ + struct bflb_ipc *ipc = to_bflb_ipc(mbox); + struct bflb_ipc_chan_info *mchan; + struct mbox_chan *chan; + struct device *dev; + int chan_id; + + dev = ipc->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (ph->args_count != 2) + return ERR_PTR(-EINVAL); + + for (chan_id = 0; chan_id < mbox->num_chans; chan_id++) { + chan = &ipc->chans[chan_id]; + mchan = chan->con_priv; + + if (!mchan) + break; + else if (mchan->client_id == ph->args[0] && + mchan->signal_id == ph->args[1]) + return ERR_PTR(-EBUSY); + } + + if (chan_id >= mbox->num_chans) + return ERR_PTR(-EBUSY); + + mchan = devm_kzalloc(dev, sizeof(*mchan), GFP_KERNEL); + if (!mchan) + return ERR_PTR(-ENOMEM); + + mchan->client_id = ph->args[0]; + mchan->signal_id = ph->args[1]; + chan->con_priv = mchan; + + return chan; +} + +static const struct mbox_chan_ops ipc_mbox_chan_ops = { + .send_data = bflb_ipc_mbox_send_data, + .shutdown = bflb_ipc_mbox_shutdown, +}; + +static int bflb_ipc_setup_mbox(struct bflb_ipc *ipc, + struct device_node *controller_dn) +{ + struct of_phandle_args curr_ph; + struct device_node *client_dn; + struct mbox_controller *mbox; + struct device *dev = ipc->dev; + int i, j, ret; + + /* + * Find out the number of clients interested in this mailbox + * and create channels accordingly. + */ + ipc->num_chans = 0; + for_each_node_with_property(client_dn, "mboxes") { + if (!of_device_is_available(client_dn)) + continue; + i = of_count_phandle_with_args(client_dn, + "mboxes", "#mbox-cells"); + for (j = 0; j < i; j++) { + ret = of_parse_phandle_with_args(client_dn, "mboxes", + "#mbox-cells", j, &curr_ph); + of_node_put(curr_ph.np); + if (!ret && curr_ph.np == controller_dn) { + ipc->num_chans++; + break; + } + } + } + + /* If no clients are found, skip registering as a mbox controller */ + if (!ipc->num_chans) + return 0; + + ipc->chans = devm_kcalloc(dev, ipc->num_chans, + sizeof(struct mbox_chan), GFP_KERNEL); + if (!ipc->chans) + return -ENOMEM; + + mbox = &ipc->mbox; + mbox->dev = dev; + mbox->num_chans = ipc->num_chans; + mbox->chans = ipc->chans; + mbox->ops = &ipc_mbox_chan_ops; + mbox->of_xlate = bflb_ipc_mbox_xlate; + mbox->txdone_irq = false; + mbox->txdone_poll = false; + + return devm_mbox_controller_register(dev, mbox); +} + +static int bflb_ipc_pm_resume(struct device *dev) +{ + return 0; +} + +static int bflb_ipc_probe(struct platform_device *pdev) +{ + struct bflb_ipc *ipc; + static int id; + int i; + char *name; + int ret; + + ipc = devm_kzalloc(&pdev->dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return -ENOMEM; + + ipc->dev = &pdev->dev; + + for (i=0; i<4; i++) { + ipc->base[i] = devm_platform_ioremap_resource(pdev, i); + if (IS_ERR(ipc->base[i])) + return PTR_ERR(ipc->base[i]); + } + + ipc->irq = platform_get_irq(pdev, 0); + if (ipc->irq < 0) + return ipc->irq; + + name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "mboxic%d", id++); + if (!name) + return -ENOMEM; + + ipc->irq_domain = irq_domain_add_tree(pdev->dev.of_node, + &bflb_ipc_irq_ops, ipc); + if (!ipc->irq_domain) + return -ENOMEM; + + ret = bflb_ipc_setup_mbox(ipc, pdev->dev.of_node); + if (ret) + goto err_mbox; + + ret = devm_request_irq(&pdev->dev, ipc->irq, bflb_ipc_irq_fn, + IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND | + IRQF_NO_THREAD, name, ipc); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret); + goto err_req_irq; + } + + platform_set_drvdata(pdev, ipc); + + dev_info(&pdev->dev, "Bouffalo Lab IPC mailbox interrupt controller"); + return 0; + +err_req_irq: + if (ipc->num_chans) + mbox_controller_unregister(&ipc->mbox); +err_mbox: + irq_domain_remove(ipc->irq_domain); + + return ret; +} + +static int bflb_ipc_remove(struct platform_device *pdev) +{ + struct bflb_ipc *ipc = platform_get_drvdata(pdev); + + disable_irq_wake(ipc->irq); + irq_domain_remove(ipc->irq_domain); + + return 0; +} + +static const struct of_device_id bflb_ipc_of_match[] = { + { .compatible = "bouffalolab,bflb-ipc"}, + {} +}; +MODULE_DEVICE_TABLE(of, bflb_ipc_of_match); + +static const struct dev_pm_ops bflb_ipc_dev_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, bflb_ipc_pm_resume) +}; + +static struct platform_driver bflb_ipc_driver = { + .probe = bflb_ipc_probe, + .remove = bflb_ipc_remove, + .driver = { + .name = "bflb-ipc", + .of_match_table = bflb_ipc_of_match, + .suppress_bind_attrs = true, + .pm = pm_sleep_ptr(&bflb_ipc_dev_pm_ops), + }, +}; + +static int __init bflb_ipc_init(void) +{ + return platform_driver_register(&bflb_ipc_driver); +} +arch_initcall(bflb_ipc_init); + +MODULE_AUTHOR("Allen Martin "); +MODULE_DESCRIPTION("Bouffalo Lab IPC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/dt-bindings/mailbox/bflb-ipc.h b/include/dt-bindings/mailbox/bflb-ipc.h new file mode 100644 index 000000000000..1d4c4be6292e --- /dev/null +++ b/include/dt-bindings/mailbox/bflb-ipc.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ +/* + * Copyright (C) 2023 Allen Martin + */ + +#ifndef __DT_BINDINGS_MAILBOX_BFLB_IPC_H +#define __DT_BINDINGS_MAILBOX_BFLB_IPC_H + +/* Source processor */ +#define BFLB_IPC_SOURCE_M0 0 +#define BFLB_IPC_SOURCE_LP 1 + +/* Peripheral device ID */ +#define BFLB_IPC_DEVICE_SDHCI 0 + +#endif -- 2.39.0