xref: /freebsd/sys/dev/mailbox/arm/arm_doorbell.c (revision fdafd315)
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