1/*-
2 * Copyright (c) 2016 Michael Zhilin <mizhka@gmail.com>
3 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
4 * Copyright (c) 2017 The FreeBSD Foundation
5 * All rights reserved.
6 *
7 * Portions of this software were developed by Landon Fuller
8 * under sponsorship from the FreeBSD Foundation.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer,
15 *    without modification.
16 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
17 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
18 *    redistribution must be conditioned upon including a substantially
19 *    similar Disclaimer requirement for further binary redistribution.
20 *
21 * NO WARRANTY
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
25 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
26 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
27 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
30 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
32 * THE POSSIBILITY OF SUCH DAMAGES.
33 */
34
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD$");
37
38#include <sys/param.h>
39#include <sys/kernel.h>
40#include <sys/bus.h>
41#include <sys/module.h>
42#include <sys/proc.h>
43
44#include <machine/bus.h>
45#include <sys/rman.h>
46
47#include <machine/cpufunc.h>
48#include <machine/intr.h>
49#include <machine/resource.h>
50
51#include <dev/bhnd/bhnd.h>
52#include <dev/bhnd/bcma/bcma_dmp.h>
53
54#include "pic_if.h"
55
56#include "bcm_machdep.h"
57
58#include "bcm_mipsvar.h"
59#include "bcm_mips74kreg.h"
60
61/*
62 * Broadcom MIPS74K Core
63 *
64 * These cores are only found on bcma(4) chipsets.
65 */
66
67struct bcm_mips74k_softc;
68
69static int	bcm_mips74k_pic_intr(void *arg);
70static void	bcm_mips74k_mask_irq(struct bcm_mips74k_softc *sc,
71		    u_int mips_irq, u_int ivec);
72static void	bcm_mips74k_unmask_irq(struct bcm_mips74k_softc *sc,
73		    u_int mips_irq, u_int ivec);
74
75static const struct bhnd_device bcm_mips74k_devs[] = {
76	BHND_DEVICE(MIPS, MIPS74K, NULL, NULL, BHND_DF_SOC),
77	BHND_DEVICE_END
78};
79
80struct bcm_mips74k_softc {
81	struct bcm_mips_softc	 bcm_mips;	/**< parent softc */
82	device_t		 dev;
83	struct resource		*mem;		/**< cpu core registers */
84	int			 mem_rid;
85};
86
87/* Early routing of the CPU timer interrupt is required */
88static void
89bcm_mips74k_timer_init(void *unused)
90{
91	struct bcm_platform	*bp;
92	u_int			 irq;
93	uint32_t		 mask;
94
95	bp = bcm_get_platform();
96
97	/* Must be a MIPS74K core attached to a BCMA interconnect */
98	if (!bhnd_core_matches(&bp->cpu_id, &(struct bhnd_core_match) {
99		BHND_MATCH_CORE(BHND_MFGID_MIPS, BHND_COREID_MIPS74K)
100	})) {
101		if (bootverbose) {
102			BCM_ERR("not a MIPS74K core: %s %s\n",
103			    bhnd_vendor_name(bp->cpu_id.vendor),
104			    bhnd_core_name(&bp->cpu_id));
105		}
106
107		return;
108	}
109
110	if (!BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(bp->cid.chip_type)) {
111		if (bootverbose)
112			BCM_ERR("not a BCMA device\n");
113		return;
114	}
115
116	/* Route the timer bus ivec to the CPU's timer IRQ, and disable any
117	 * other vectors assigned to the IRQ. */
118	irq = BCM_MIPS74K_GET_TIMER_IRQ();
119	mask = BCM_MIPS74K_INTR_SEL_FLAG(BCM_MIPS74K_TIMER_IVEC);
120
121	BCM_CPU_WRITE_4(bp, BCM_MIPS74K_INTR_SEL(irq), mask);
122}
123
124static int
125bcm_mips74k_probe(device_t dev)
126{
127	const struct bhnd_device	*id;
128	const struct bhnd_chipid	*cid;
129
130	id = bhnd_device_lookup(dev, bcm_mips74k_devs,
131	    sizeof(bcm_mips74k_devs[0]));
132	if (id == NULL)
133		return (ENXIO);
134
135	/* Check the chip type; the MIPS74K core should only be found
136	 * on bcma(4) chipsets (and we rely on bcma OOB interrupt
137	 * routing). */
138	cid = bhnd_get_chipid(dev);
139	if (!BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(cid->chip_type))
140		return (ENXIO);
141
142	bhnd_set_default_core_desc(dev);
143	return (BUS_PROBE_DEFAULT);
144}
145
146static int
147bcm_mips74k_attach(device_t dev)
148{
149	struct bcm_mips74k_softc	*sc;
150	u_int				 timer_irq;
151	int				 error;
152
153	sc = device_get_softc(dev);
154	sc->dev = dev;
155
156	/* Allocate our core's register block */
157	sc->mem_rid = 0;
158	sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
159	    RF_ACTIVE);
160	if (sc->mem == NULL) {
161		device_printf(dev, "failed to allocate cpu register block\n");
162		return (ENXIO);
163	}
164
165	/* Clear interrupt map */
166	timer_irq = BCM_MIPS74K_GET_TIMER_IRQ();
167	for (size_t i = 0; i < BCM_MIPS74K_NUM_INTR; i++) {
168		/* We don't use the timer IRQ; leave it routed to the
169		 * MIPS CPU */
170		if (i == timer_irq)
171			continue;
172
173		bus_write_4(sc->mem, BCM_MIPS74K_INTR_SEL(i), 0);
174	}
175
176	/* Initialize the generic BHND MIPS driver state */
177	error = bcm_mips_attach(dev, BCM_MIPS74K_NUM_INTR, timer_irq,
178	    bcm_mips74k_pic_intr);
179	if (error) {
180		bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
181		return (error);
182	}
183
184	return (0);
185}
186
187static int
188bcm_mips74k_detach(device_t dev)
189{
190	struct bcm_mips74k_softc	*sc;
191	int				 error;
192
193	sc = device_get_softc(dev);
194
195	if ((error = bcm_mips_detach(dev)))
196		return (error);
197
198	bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
199
200	return (0);
201}
202
203
204/* PIC_DISABLE_INTR() */
205static void
206bcm_mips74k_pic_disable_intr(device_t dev, struct intr_irqsrc *irqsrc)
207{
208	struct bcm_mips74k_softc	*sc;
209	struct bcm_mips_irqsrc		*isrc;
210
211	sc = device_get_softc(dev);
212	isrc = (struct bcm_mips_irqsrc *)irqsrc;
213
214	KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ"));
215
216	bcm_mips74k_mask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec);
217}
218
219/* PIC_ENABLE_INTR() */
220static void
221bcm_mips74k_pic_enable_intr(device_t dev, struct intr_irqsrc *irqsrc)
222{
223	struct bcm_mips74k_softc	*sc;
224	struct bcm_mips_irqsrc		*isrc;
225
226	sc = device_get_softc(dev);
227	isrc = (struct bcm_mips_irqsrc *)irqsrc;
228
229	KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ"));
230
231	bcm_mips74k_unmask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec);
232}
233
234/* PIC_PRE_ITHREAD() */
235static void
236bcm_mips74k_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
237{
238	bcm_mips74k_pic_disable_intr(dev, isrc);
239}
240
241/* PIC_POST_ITHREAD() */
242static void
243bcm_mips74k_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc)
244{
245	bcm_mips74k_pic_enable_intr(dev, isrc);
246}
247
248/* PIC_POST_FILTER() */
249static void
250bcm_mips74k_pic_post_filter(device_t dev, struct intr_irqsrc *isrc)
251{
252}
253
254/**
255 * Disable routing of backplane interrupt vector @p ivec to MIPS IRQ
256 * @p mips_irq.
257 */
258static void
259bcm_mips74k_mask_irq(struct bcm_mips74k_softc *sc, u_int mips_irq, u_int ivec)
260{
261	uint32_t oobsel;
262
263	KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u",
264	    mips_irq));
265	KASSERT(mips_irq < BCM_MIPS74K_NUM_INTR, ("unsupported MIPS IRQ %u",
266	    mips_irq));
267	KASSERT(ivec < BCMA_OOB_NUM_BUSLINES, ("invalid backplane ivec"));
268
269	oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq));
270	oobsel &= ~(BCM_MIPS74K_INTR_SEL_FLAG(ivec));
271	bus_write_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq), oobsel);
272}
273
274/**
275 * Enable routing of an interrupt.
276 */
277static void
278bcm_mips74k_unmask_irq(struct bcm_mips74k_softc *sc, u_int mips_irq, u_int ivec)
279{
280	uint32_t oobsel;
281
282	KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u",
283	    mips_irq));
284	KASSERT(mips_irq < BCM_MIPS74K_NUM_INTR, ("unsupported MIPS IRQ %u",
285	    mips_irq));
286	KASSERT(ivec < BCMA_OOB_NUM_BUSLINES, ("invalid backplane ivec"));
287
288	oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq));
289	oobsel |= BCM_MIPS74K_INTR_SEL_FLAG(ivec);
290	bus_write_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq), oobsel);
291}
292
293/* our MIPS CPU interrupt filter */
294static int
295bcm_mips74k_pic_intr(void *arg)
296{
297	struct bcm_mips74k_softc	*sc;
298	struct bcm_mips_cpuirq		*cpuirq;
299	struct bcm_mips_irqsrc		*isrc_solo;
300	uint32_t			 oobsel, intr;
301	u_int				 i;
302	int				 error;
303
304	cpuirq = arg;
305	sc = (struct bcm_mips74k_softc*)cpuirq->sc;
306
307	/* Fetch current interrupt state */
308	intr = bus_read_4(sc->mem, BCM_MIPS74K_INTR_STATUS);
309
310	/* Fetch mask of interrupt vectors routed to this MIPS IRQ */
311	KASSERT(cpuirq->mips_irq < BCM_MIPS74K_NUM_INTR,
312	    ("invalid irq %u", cpuirq->mips_irq));
313
314	oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(cpuirq->mips_irq));
315
316	/* Ignore interrupts not routed to this MIPS IRQ */
317	intr &= oobsel;
318
319	/* Handle isrc_solo direct dispatch path */
320	isrc_solo = cpuirq->isrc_solo;
321	if (isrc_solo != NULL) {
322		if (intr & BCM_MIPS_IVEC_MASK(isrc_solo)) {
323			error = intr_isrc_dispatch(&isrc_solo->isrc,
324			    curthread->td_intr_frame);
325			if (error) {
326				device_printf(sc->dev, "Stray interrupt %u "
327				    "detected\n", isrc_solo->ivec);
328				bcm_mips74k_pic_disable_intr(sc->dev,
329				    &isrc_solo->isrc);
330			}
331		}
332
333		intr &= ~(BCM_MIPS_IVEC_MASK(isrc_solo));
334		if (intr == 0)
335			return (FILTER_HANDLED);
336
337		/* Report and mask additional stray interrupts */
338		while ((i = fls(intr)) != 0) {
339			i--; /* Get a 0-offset interrupt. */
340			intr &= ~(1 << i);
341
342			device_printf(sc->dev, "Stray interrupt %u "
343				"detected\n", i);
344			bcm_mips74k_mask_irq(sc, cpuirq->mips_irq, i);
345		}
346
347		return (FILTER_HANDLED);
348	}
349
350	/* Standard dispatch path  */
351	while ((i = fls(intr)) != 0) {
352		i--; /* Get a 0-offset interrupt. */
353		intr &= ~(1 << i);
354
355		KASSERT(i < nitems(sc->bcm_mips.isrcs), ("invalid ivec %u", i));
356
357		error = intr_isrc_dispatch(&sc->bcm_mips.isrcs[i].isrc,
358		    curthread->td_intr_frame);
359		if (error) {
360			device_printf(sc->dev, "Stray interrupt %u detected\n",
361			    i);
362			bcm_mips74k_mask_irq(sc, cpuirq->mips_irq, i);
363			continue;
364		}
365	}
366
367	return (FILTER_HANDLED);
368}
369
370static device_method_t bcm_mips74k_methods[] = {
371	/* Device interface */
372	DEVMETHOD(device_probe,		bcm_mips74k_probe),
373	DEVMETHOD(device_attach,	bcm_mips74k_attach),
374	DEVMETHOD(device_detach,	bcm_mips74k_detach),
375
376	/* Interrupt controller interface */
377	DEVMETHOD(pic_disable_intr,	bcm_mips74k_pic_disable_intr),
378	DEVMETHOD(pic_enable_intr,	bcm_mips74k_pic_enable_intr),
379	DEVMETHOD(pic_pre_ithread,	bcm_mips74k_pic_pre_ithread),
380	DEVMETHOD(pic_post_ithread,	bcm_mips74k_pic_post_ithread),
381	DEVMETHOD(pic_post_filter,	bcm_mips74k_pic_post_filter),
382
383	DEVMETHOD_END
384};
385
386static devclass_t bcm_mips_devclass;
387
388DEFINE_CLASS_1(bcm_mips, bcm_mips74k_driver, bcm_mips74k_methods, sizeof(struct bcm_mips_softc), bcm_mips_driver);
389EARLY_DRIVER_MODULE(bcm_mips74k, bhnd, bcm_mips74k_driver, bcm_mips_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
390SYSINIT(cpu_init, SI_SUB_CPU, SI_ORDER_FIRST, bcm_mips74k_timer_init, NULL);
391MODULE_VERSION(bcm_mips74k, 1);
392MODULE_DEPEND(bcm_mips74k, bhnd, 1, 1, 1);
393