154b96380SRuslan Bukin /*-
254b96380SRuslan Bukin * SPDX-License-Identifier: BSD-2-Clause
354b96380SRuslan Bukin *
454b96380SRuslan Bukin * Copyright (c) 2022 Ruslan Bukin <br@bsdpad.com>
554b96380SRuslan Bukin *
654b96380SRuslan Bukin * This work was supported by Innovate UK project 105694, "Digital Security
754b96380SRuslan Bukin * by Design (DSbD) Technology Platform Prototype".
854b96380SRuslan Bukin *
954b96380SRuslan Bukin * Redistribution and use in source and binary forms, with or without
1054b96380SRuslan Bukin * modification, are permitted provided that the following conditions
1154b96380SRuslan Bukin * are met:
1254b96380SRuslan Bukin * 1. Redistributions of source code must retain the above copyright
1354b96380SRuslan Bukin * notice, this list of conditions and the following disclaimer.
1454b96380SRuslan Bukin * 2. Redistributions in binary form must reproduce the above copyright
1554b96380SRuslan Bukin * notice, this list of conditions and the following disclaimer in the
1654b96380SRuslan Bukin * documentation and/or other materials provided with the distribution.
1754b96380SRuslan Bukin *
1854b96380SRuslan Bukin * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1954b96380SRuslan Bukin * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2054b96380SRuslan Bukin * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2154b96380SRuslan Bukin * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2254b96380SRuslan Bukin * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2354b96380SRuslan Bukin * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2454b96380SRuslan Bukin * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2554b96380SRuslan Bukin * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2654b96380SRuslan Bukin * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2754b96380SRuslan Bukin * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2854b96380SRuslan Bukin * SUCH DAMAGE.
2954b96380SRuslan Bukin */
3054b96380SRuslan Bukin
3154b96380SRuslan Bukin #include <sys/param.h>
3254b96380SRuslan Bukin #include <sys/systm.h>
3354b96380SRuslan Bukin #include <sys/bus.h>
3454b96380SRuslan Bukin #include <sys/rman.h>
3554b96380SRuslan Bukin #include <sys/kernel.h>
3654b96380SRuslan Bukin #include <sys/module.h>
3754b96380SRuslan Bukin
3854b96380SRuslan Bukin #include <machine/bus.h>
3954b96380SRuslan Bukin
4054b96380SRuslan Bukin #include <dev/fdt/simplebus.h>
4154b96380SRuslan Bukin #include <dev/fdt/fdt_common.h>
4254b96380SRuslan Bukin #include <dev/ofw/ofw_bus_subr.h>
4354b96380SRuslan Bukin
4454b96380SRuslan Bukin #include "arm_doorbell.h"
4554b96380SRuslan Bukin
4654b96380SRuslan Bukin #define MHU_CHAN_RX_LP 0x000 /* Low priority channel */
4754b96380SRuslan Bukin #define MHU_CHAN_RX_HP 0x020 /* High priority channel */
4854b96380SRuslan Bukin #define MHU_CHAN_RX_SEC 0x200 /* Secure channel */
4954b96380SRuslan Bukin #define MHU_INTR_STAT 0x00
5054b96380SRuslan Bukin #define MHU_INTR_SET 0x08
5154b96380SRuslan Bukin #define MHU_INTR_CLEAR 0x10
5254b96380SRuslan Bukin
5354b96380SRuslan Bukin #define MHU_TX_REG_OFFSET 0x100
5454b96380SRuslan Bukin
5554b96380SRuslan Bukin #define DOORBELL_N_CHANNELS 3
5654b96380SRuslan Bukin #define DOORBELL_N_DOORBELLS (DOORBELL_N_CHANNELS * 32)
5754b96380SRuslan Bukin
5854b96380SRuslan Bukin struct arm_doorbell dbells[DOORBELL_N_DOORBELLS];
5954b96380SRuslan Bukin
6054b96380SRuslan Bukin static struct resource_spec arm_doorbell_spec[] = {
6154b96380SRuslan Bukin { SYS_RES_MEMORY, 0, RF_ACTIVE },
6254b96380SRuslan Bukin { SYS_RES_IRQ, 0, RF_ACTIVE },
6354b96380SRuslan Bukin { SYS_RES_IRQ, 1, RF_ACTIVE },
6454b96380SRuslan Bukin { -1, 0 }
6554b96380SRuslan Bukin };
6654b96380SRuslan Bukin
6754b96380SRuslan Bukin struct arm_doorbell_softc {
6854b96380SRuslan Bukin struct resource *res[3];
6954b96380SRuslan Bukin void *lp_intr_cookie;
7054b96380SRuslan Bukin void *hp_intr_cookie;
7154b96380SRuslan Bukin device_t dev;
7254b96380SRuslan Bukin };
7354b96380SRuslan Bukin
7454b96380SRuslan Bukin static void
arm_doorbell_lp_intr(void * arg)7554b96380SRuslan Bukin arm_doorbell_lp_intr(void *arg)
7654b96380SRuslan Bukin {
7754b96380SRuslan Bukin struct arm_doorbell_softc *sc;
7854b96380SRuslan Bukin struct arm_doorbell *db;
7954b96380SRuslan Bukin uint32_t reg;
8054b96380SRuslan Bukin int i;
8154b96380SRuslan Bukin
8254b96380SRuslan Bukin sc = arg;
8354b96380SRuslan Bukin
8454b96380SRuslan Bukin reg = bus_read_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_STAT);
8554b96380SRuslan Bukin for (i = 0; i < 32; i++) {
8654b96380SRuslan Bukin if (reg & (1 << i)) {
8754b96380SRuslan Bukin db = &dbells[i];
8854b96380SRuslan Bukin bus_write_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_CLEAR,
8954b96380SRuslan Bukin (1 << i));
9054b96380SRuslan Bukin if (db->func != NULL)
9154b96380SRuslan Bukin db->func(db->arg);
9254b96380SRuslan Bukin }
9354b96380SRuslan Bukin }
9454b96380SRuslan Bukin }
9554b96380SRuslan Bukin
9654b96380SRuslan Bukin static void
arm_doorbell_hp_intr(void * arg)9754b96380SRuslan Bukin arm_doorbell_hp_intr(void *arg)
9854b96380SRuslan Bukin {
9954b96380SRuslan Bukin struct arm_doorbell_softc *sc;
10054b96380SRuslan Bukin struct arm_doorbell *db;
10154b96380SRuslan Bukin uint32_t reg;
10254b96380SRuslan Bukin int i;
10354b96380SRuslan Bukin
10454b96380SRuslan Bukin sc = arg;
10554b96380SRuslan Bukin
10654b96380SRuslan Bukin reg = bus_read_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_STAT);
10754b96380SRuslan Bukin for (i = 0; i < 32; i++) {
10854b96380SRuslan Bukin if (reg & (1 << i)) {
10954b96380SRuslan Bukin db = &dbells[i];
11054b96380SRuslan Bukin bus_write_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_CLEAR,
11154b96380SRuslan Bukin (1 << i));
11254b96380SRuslan Bukin if (db->func != NULL)
11354b96380SRuslan Bukin db->func(db->arg);
11454b96380SRuslan Bukin }
11554b96380SRuslan Bukin }
11654b96380SRuslan Bukin }
11754b96380SRuslan Bukin
11854b96380SRuslan Bukin static int
arm_doorbell_probe(device_t dev)11954b96380SRuslan Bukin arm_doorbell_probe(device_t dev)
12054b96380SRuslan Bukin {
12154b96380SRuslan Bukin
12254b96380SRuslan Bukin if (!ofw_bus_is_compatible(dev, "arm,mhu-doorbell"))
12354b96380SRuslan Bukin return (ENXIO);
12454b96380SRuslan Bukin
12554b96380SRuslan Bukin if (!ofw_bus_status_okay(dev))
12654b96380SRuslan Bukin return (ENXIO);
12754b96380SRuslan Bukin
12854b96380SRuslan Bukin device_set_desc(dev, "ARM MHU Doorbell");
12954b96380SRuslan Bukin
13054b96380SRuslan Bukin return (BUS_PROBE_DEFAULT);
13154b96380SRuslan Bukin }
13254b96380SRuslan Bukin
13354b96380SRuslan Bukin static int
arm_doorbell_attach(device_t dev)13454b96380SRuslan Bukin arm_doorbell_attach(device_t dev)
13554b96380SRuslan Bukin {
13654b96380SRuslan Bukin struct arm_doorbell_softc *sc;
13754b96380SRuslan Bukin phandle_t node;
13854b96380SRuslan Bukin int error;
13954b96380SRuslan Bukin
14054b96380SRuslan Bukin sc = device_get_softc(dev);
14154b96380SRuslan Bukin sc->dev = dev;
14254b96380SRuslan Bukin
14354b96380SRuslan Bukin node = ofw_bus_get_node(dev);
14454b96380SRuslan Bukin if (node == -1)
14554b96380SRuslan Bukin return (ENXIO);
14654b96380SRuslan Bukin
14754b96380SRuslan Bukin if (bus_alloc_resources(dev, arm_doorbell_spec, sc->res) != 0) {
14854b96380SRuslan Bukin device_printf(dev, "Can't allocate resources for device.\n");
14954b96380SRuslan Bukin return (ENXIO);
15054b96380SRuslan Bukin }
15154b96380SRuslan Bukin
15254b96380SRuslan Bukin /* Setup interrupt handlers. */
15354b96380SRuslan Bukin error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE,
15454b96380SRuslan Bukin NULL, arm_doorbell_lp_intr, sc, &sc->lp_intr_cookie);
15554b96380SRuslan Bukin if (error != 0) {
15654b96380SRuslan Bukin device_printf(dev, "Can't setup LP interrupt handler.\n");
15754b96380SRuslan Bukin bus_release_resources(dev, arm_doorbell_spec, sc->res);
15854b96380SRuslan Bukin return (ENXIO);
15954b96380SRuslan Bukin }
16054b96380SRuslan Bukin
16154b96380SRuslan Bukin error = bus_setup_intr(dev, sc->res[2], INTR_TYPE_MISC | INTR_MPSAFE,
16254b96380SRuslan Bukin NULL, arm_doorbell_hp_intr, sc, &sc->hp_intr_cookie);
16354b96380SRuslan Bukin if (error != 0) {
16454b96380SRuslan Bukin device_printf(dev, "Can't setup HP interrupt handler.\n");
16554b96380SRuslan Bukin bus_release_resources(dev, arm_doorbell_spec, sc->res);
16654b96380SRuslan Bukin return (ENXIO);
16754b96380SRuslan Bukin }
16854b96380SRuslan Bukin
16954b96380SRuslan Bukin OF_device_register_xref(OF_xref_from_node(node), dev);
17054b96380SRuslan Bukin
17154b96380SRuslan Bukin return (0);
17254b96380SRuslan Bukin }
17354b96380SRuslan Bukin
17454b96380SRuslan Bukin static int
arm_doorbell_detach(device_t dev)17554b96380SRuslan Bukin arm_doorbell_detach(device_t dev)
17654b96380SRuslan Bukin {
17754b96380SRuslan Bukin
17854b96380SRuslan Bukin return (EBUSY);
17954b96380SRuslan Bukin }
18054b96380SRuslan Bukin
18154b96380SRuslan Bukin struct arm_doorbell *
arm_doorbell_ofw_get(device_t dev,const char * name)18254b96380SRuslan Bukin arm_doorbell_ofw_get(device_t dev, const char *name)
18354b96380SRuslan Bukin {
18454b96380SRuslan Bukin phandle_t node, parent;
18554b96380SRuslan Bukin struct arm_doorbell *db;
18654b96380SRuslan Bukin device_t db_dev;
18754b96380SRuslan Bukin pcell_t *cells;
18854b96380SRuslan Bukin int nmboxes;
18954b96380SRuslan Bukin int ncells;
19054b96380SRuslan Bukin int idx;
19154b96380SRuslan Bukin int db_id;
19254b96380SRuslan Bukin int error;
19354b96380SRuslan Bukin int chan;
19454b96380SRuslan Bukin
19554b96380SRuslan Bukin node = ofw_bus_get_node(dev);
19654b96380SRuslan Bukin
19754b96380SRuslan Bukin error = ofw_bus_parse_xref_list_get_length(node, "mboxes",
19854b96380SRuslan Bukin "#mbox-cells", &nmboxes);
19954b96380SRuslan Bukin if (error) {
20054b96380SRuslan Bukin device_printf(dev, "%s can't get mboxes list.\n", __func__);
20154b96380SRuslan Bukin return (NULL);
20254b96380SRuslan Bukin }
20354b96380SRuslan Bukin
20454b96380SRuslan Bukin if (nmboxes == 0) {
20554b96380SRuslan Bukin device_printf(dev, "%s mbox list is empty.\n", __func__);
20654b96380SRuslan Bukin return (NULL);
20754b96380SRuslan Bukin }
20854b96380SRuslan Bukin
20954b96380SRuslan Bukin error = ofw_bus_find_string_index(node, "mbox-names", name, &idx);
21054b96380SRuslan Bukin if (error != 0) {
21154b96380SRuslan Bukin device_printf(dev, "%s can't find string index.\n",
21254b96380SRuslan Bukin __func__);
21354b96380SRuslan Bukin return (NULL);
21454b96380SRuslan Bukin }
21554b96380SRuslan Bukin
21654b96380SRuslan Bukin error = ofw_bus_parse_xref_list_alloc(node, "mboxes", "#mbox-cells",
21754b96380SRuslan Bukin idx, &parent, &ncells, &cells);
21854b96380SRuslan Bukin if (error != 0) {
21954b96380SRuslan Bukin device_printf(dev, "%s can't get mbox device xref\n",
22054b96380SRuslan Bukin __func__);
22154b96380SRuslan Bukin return (NULL);
22254b96380SRuslan Bukin }
22354b96380SRuslan Bukin
22454b96380SRuslan Bukin if (ncells != 2) {
22554b96380SRuslan Bukin device_printf(dev, "Unexpected data size.\n");
22654b96380SRuslan Bukin OF_prop_free(cells);
22754b96380SRuslan Bukin return (NULL);
22854b96380SRuslan Bukin }
22954b96380SRuslan Bukin
23054b96380SRuslan Bukin db_dev = OF_device_from_xref(parent);
23154b96380SRuslan Bukin if (db_dev == NULL) {
23254b96380SRuslan Bukin device_printf(dev, "%s: Can't get arm_doorbell device\n",
23354b96380SRuslan Bukin __func__);
23454b96380SRuslan Bukin OF_prop_free(cells);
23554b96380SRuslan Bukin return (NULL);
23654b96380SRuslan Bukin }
23754b96380SRuslan Bukin
23854b96380SRuslan Bukin chan = cells[0];
23954b96380SRuslan Bukin if (chan >= DOORBELL_N_CHANNELS) {
24054b96380SRuslan Bukin device_printf(dev, "Unexpected channel number.\n");
24154b96380SRuslan Bukin OF_prop_free(cells);
24254b96380SRuslan Bukin return (NULL);
24354b96380SRuslan Bukin }
24454b96380SRuslan Bukin
24554b96380SRuslan Bukin db_id = cells[1];
24654b96380SRuslan Bukin if (db_id >= 32) {
24754b96380SRuslan Bukin device_printf(dev, "Unexpected channel bit.\n");
24854b96380SRuslan Bukin OF_prop_free(cells);
24954b96380SRuslan Bukin return (NULL);
25054b96380SRuslan Bukin }
25154b96380SRuslan Bukin
25254b96380SRuslan Bukin db = &dbells[chan * db_id];
25354b96380SRuslan Bukin db->dev = dev;
25454b96380SRuslan Bukin db->db_dev = db_dev;
25554b96380SRuslan Bukin db->chan = chan;
25654b96380SRuslan Bukin db->db = db_id;
25754b96380SRuslan Bukin
25854b96380SRuslan Bukin OF_prop_free(cells);
25954b96380SRuslan Bukin
26054b96380SRuslan Bukin return (db);
26154b96380SRuslan Bukin }
26254b96380SRuslan Bukin
26354b96380SRuslan Bukin void
arm_doorbell_set(struct arm_doorbell * db)26454b96380SRuslan Bukin arm_doorbell_set(struct arm_doorbell *db)
26554b96380SRuslan Bukin {
26654b96380SRuslan Bukin struct arm_doorbell_softc *sc;
26754b96380SRuslan Bukin uint32_t offset;
26854b96380SRuslan Bukin
26954b96380SRuslan Bukin sc = device_get_softc(db->db_dev);
27054b96380SRuslan Bukin
27154b96380SRuslan Bukin switch (db->chan) {
27254b96380SRuslan Bukin case 0:
27354b96380SRuslan Bukin offset = MHU_CHAN_RX_LP;
27454b96380SRuslan Bukin break;
27554b96380SRuslan Bukin case 1:
27654b96380SRuslan Bukin offset = MHU_CHAN_RX_HP;
27754b96380SRuslan Bukin break;
27854b96380SRuslan Bukin case 2:
27954b96380SRuslan Bukin offset = MHU_CHAN_RX_SEC;
28054b96380SRuslan Bukin break;
28154b96380SRuslan Bukin default:
28254b96380SRuslan Bukin panic("not reached");
28354b96380SRuslan Bukin };
28454b96380SRuslan Bukin
28554b96380SRuslan Bukin offset |= MHU_TX_REG_OFFSET;
28654b96380SRuslan Bukin
28754b96380SRuslan Bukin bus_write_4(sc->res[0], offset + MHU_INTR_SET, (1 << db->db));
28854b96380SRuslan Bukin }
28954b96380SRuslan Bukin
29054b96380SRuslan Bukin int
arm_doorbell_get(struct arm_doorbell * db)29154b96380SRuslan Bukin arm_doorbell_get(struct arm_doorbell *db)
29254b96380SRuslan Bukin {
29354b96380SRuslan Bukin struct arm_doorbell_softc *sc;
29454b96380SRuslan Bukin uint32_t offset;
29554b96380SRuslan Bukin uint32_t reg;
29654b96380SRuslan Bukin
29754b96380SRuslan Bukin sc = device_get_softc(db->db_dev);
29854b96380SRuslan Bukin
29954b96380SRuslan Bukin switch (db->chan) {
30054b96380SRuslan Bukin case 0:
30154b96380SRuslan Bukin offset = MHU_CHAN_RX_LP;
30254b96380SRuslan Bukin break;
30354b96380SRuslan Bukin case 1:
30454b96380SRuslan Bukin offset = MHU_CHAN_RX_HP;
30554b96380SRuslan Bukin break;
30654b96380SRuslan Bukin case 2:
30754b96380SRuslan Bukin offset = MHU_CHAN_RX_SEC;
30854b96380SRuslan Bukin break;
30954b96380SRuslan Bukin default:
31054b96380SRuslan Bukin panic("not reached");
31154b96380SRuslan Bukin };
31254b96380SRuslan Bukin
31354b96380SRuslan Bukin reg = bus_read_4(sc->res[0], offset + MHU_INTR_STAT);
31454b96380SRuslan Bukin if (reg & (1 << db->db)) {
31554b96380SRuslan Bukin bus_write_4(sc->res[0], offset + MHU_INTR_CLEAR,
31654b96380SRuslan Bukin (1 << db->db));
31754b96380SRuslan Bukin return (1);
31854b96380SRuslan Bukin }
31954b96380SRuslan Bukin
32054b96380SRuslan Bukin return (0);
32154b96380SRuslan Bukin }
32254b96380SRuslan Bukin
32354b96380SRuslan Bukin void
arm_doorbell_set_handler(struct arm_doorbell * db,void (* func)(void *),void * arg)32454b96380SRuslan Bukin arm_doorbell_set_handler(struct arm_doorbell *db, void (*func)(void *),
32554b96380SRuslan Bukin void *arg)
32654b96380SRuslan Bukin {
32754b96380SRuslan Bukin
32854b96380SRuslan Bukin db->func = func;
32954b96380SRuslan Bukin db->arg = arg;
33054b96380SRuslan Bukin }
33154b96380SRuslan Bukin
33254b96380SRuslan Bukin static device_method_t arm_doorbell_methods[] = {
33354b96380SRuslan Bukin DEVMETHOD(device_probe, arm_doorbell_probe),
33454b96380SRuslan Bukin DEVMETHOD(device_attach, arm_doorbell_attach),
33554b96380SRuslan Bukin DEVMETHOD(device_detach, arm_doorbell_detach),
33654b96380SRuslan Bukin DEVMETHOD_END
33754b96380SRuslan Bukin };
33854b96380SRuslan Bukin
33954b96380SRuslan Bukin DEFINE_CLASS_1(arm_doorbell, arm_doorbell_driver, arm_doorbell_methods,
34054b96380SRuslan Bukin sizeof(struct arm_doorbell_softc), simplebus_driver);
34154b96380SRuslan Bukin
34254b96380SRuslan Bukin EARLY_DRIVER_MODULE(arm_doorbell, simplebus, arm_doorbell_driver, 0, 0,
34354b96380SRuslan Bukin BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
34454b96380SRuslan Bukin MODULE_VERSION(arm_doorbell, 1);
345