/*
 * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Apple T2 Buffer Copy Engine (BCE) PCI driver.
 * Provides the transport layer for T2 coprocessor communication:
 * mailbox handshake, DMA queue setup, and firmware keepalive.
 */

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/sema.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/rman.h>
#include <sys/time.h>

#include <machine/bus.h>
#include <machine/resource.h>
#include <machine/atomic.h>

#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>

#include "apple_bce.h"
#include "apple_bce_mailbox.h"
#include "apple_bce_queue.h"
#include "apple_bce_vhci.h"

static int	apple_bce_probe(device_t dev);
static int	apple_bce_attach(device_t dev);
static int	apple_bce_detach(device_t dev);
static int	apple_bce_suspend(device_t dev);
static int	apple_bce_resume(device_t dev);
static void	apple_bce_timestamp_cb(void *arg);
static void	apple_bce_timestamp_init(struct apple_bce_softc *sc);
static void	apple_bce_timestamp_start(struct apple_bce_softc *sc,
		    int is_initial);
static void	apple_bce_timestamp_stop(struct apple_bce_softc *sc);

/*
 * Timestamp protocol -- T2 firmware expects periodic boottime updates.
 *
 * Register layout at BAR4 + 0xC000:
 *   regb + 0 (0xC000): high 32 bits / control
 *   regb + 8 (0xC008): low 32 bits / data
 *
 * Control opcodes (written to regb+8 with 0xFFFFFFFF to regb+0):
 *   -4 (0xFFFFFFFC): initial start
 *   -3 (0xFFFFFFFD): restart
 *   -2 (0xFFFFFFFE): stop
 */
static void
apple_bce_timestamp_init(struct apple_bce_softc *sc)
{
	/* Read control register and barrier to sync with firmware */
	bus_read_4(sc->sc_bar4, BCE_REG_TIMESTAMP);
	mb();
}

static void
apple_bce_timestamp_start(struct apple_bce_softc *sc, int is_initial)
{
	/* Send start opcode: -4 for initial, -3 for restart */
	bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP + 8,
	    is_initial ? 0xFFFFFFFC : 0xFFFFFFFD);
	bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP, 0xFFFFFFFF);

	mtx_lock_spin(&sc->sc_timestamp_lock);
	sc->sc_timestamp_stopped = 0;
	mtx_unlock_spin(&sc->sc_timestamp_lock);

	callout_reset(&sc->sc_timestamp_co, hz * BCE_TIMESTAMP_MS / 1000,
	    apple_bce_timestamp_cb, sc);
}

static void
apple_bce_timestamp_stop(struct apple_bce_softc *sc)
{
	mtx_lock_spin(&sc->sc_timestamp_lock);
	sc->sc_timestamp_stopped = 1;
	mtx_unlock_spin(&sc->sc_timestamp_lock);

	callout_drain(&sc->sc_timestamp_co);

	/* Send stop opcode */
	bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP + 8, 0xFFFFFFFE);
	bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP, 0xFFFFFFFF);
}

static void
apple_bce_timestamp_cb(void *arg)
{
	struct apple_bce_softc *sc = arg;
	struct bintime bt;
	uint64_t ns;

	/* Read to sync, then barrier */
	bus_read_4(sc->sc_bar4, BCE_REG_TIMESTAMP + 8);
	mb();

	/* Get boot time in nanoseconds */
	binuptime(&bt);
	ns = (uint64_t)bt.sec * 1000000000ULL +
	    (((uint64_t)(bt.frac >> 32) * 1000000000ULL) >> 32);

	/* Write: low 32 bits to regb+8, high 32 bits to regb+0 */
	bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP + 8, (uint32_t)ns);
	bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP, (uint32_t)(ns >> 32));

	mtx_lock_spin(&sc->sc_timestamp_lock);
	if (!sc->sc_timestamp_stopped)
		callout_schedule(&sc->sc_timestamp_co,
		    hz * BCE_TIMESTAMP_MS / 1000);
	mtx_unlock_spin(&sc->sc_timestamp_lock);
}

/*
 * IRQ handlers.
 */
static void
apple_bce_mbox_intr(void *arg)
{
	struct apple_bce_softc *sc = arg;

	bce_mailbox_handle_interrupt(&sc->sc_mbox);
}

static void
apple_bce_dma_intr(void *arg)
{
	struct apple_bce_softc *sc = arg;
	int i;

	/*
	 * Only process registered CQs. sc_cq_list[] contains only CQ
	 * pointers -- separate from sc_queues[] which mixes CQ and SQ.
	 */
	mtx_lock(&sc->sc_queues_lock);
	for (i = 0; i < BCE_MAX_CQ_COUNT; i++) {
		if (sc->sc_cq_list[i] != NULL)
			bce_handle_cq_completions(sc, sc->sc_cq_list[i]);
	}
	mtx_unlock(&sc->sc_queues_lock);
}

/*
 * Enable bus master on PCI function 0 (NVMe).
 * T2 requires function 0 to be bus master for DMA on function 1.
 */
static int
apple_bce_enable_pci0_busmaster(device_t dev)
{
	device_t pci0;
	device_t bus;

	bus = device_get_parent(dev);
	if (bus == NULL)
		return (ENXIO);

	/*
	 * Find function 0 on the same bus/slot.
	 * Our device is function 1; function 0 is NVMe.
	 */
	pci0 = pci_find_dbsf(pci_get_domain(dev), pci_get_bus(dev),
	    pci_get_slot(dev), 0);
	if (pci0 == NULL) {
		device_printf(dev, "cannot find PCI function 0\n");
		return (ENXIO);
	}

	pci_enable_busmaster(pci0);
	device_printf(dev, "enabled bus master on function 0 (%s)\n",
	    device_get_nameunit(pci0));
	return (0);
}

/*
 * Firmware handshake via mailbox.
 */
static int
apple_bce_fw_handshake(struct apple_bce_softc *sc)
{
	uint64_t reply;
	int error;

	error = bce_mailbox_send(&sc->sc_mbox,
	    BCE_MB_MSG(BCE_MB_SET_FW_PROTOCOL_VER, BCE_FW_PROTOCOL_VER),
	    &reply, BCE_MBOX_TIMEOUT_MS);
	if (error != 0) {
		device_printf(sc->sc_dev, "firmware handshake timeout\n");
		return (error);
	}

	if (BCE_MB_TYPE(reply) != BCE_MB_SET_FW_PROTOCOL_VER ||
	    BCE_MB_VALUE(reply) != BCE_FW_PROTOCOL_VER) {
		device_printf(sc->sc_dev,
		    "firmware version mismatch: got type=%u val=0x%llx\n",
		    BCE_MB_TYPE(reply),
		    (unsigned long long)BCE_MB_VALUE(reply));
		return (ENODEV);
	}

	device_printf(sc->sc_dev, "firmware handshake OK (protocol 0x%x)\n",
	    BCE_FW_PROTOCOL_VER);
	return (0);
}

/*
 * DMA callback for command queue registration.
 */
static void
bce_reg_dma_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
	struct { bus_addr_t addr; int error; } *cb = arg;

	cb->error = error;
	if (error == 0)
		cb->addr = segs[0].ds_addr;
}

/*
 * Register a command queue (CQ or SQ) with firmware via mailbox.
 * The memcfg struct is DMA-mapped and its physical address sent
 * in the mailbox message -- firmware reads the config from DMA.
 */
static int
apple_bce_register_cmd_queue(struct apple_bce_softc *sc,
    struct bce_queue_memcfg *cfg, int is_sq)
{
	bus_dma_tag_t tag;
	bus_dmamap_t map;
	bus_addr_t paddr;
	struct bce_queue_memcfg *dma_cfg;
	struct {
		bus_addr_t addr;
		int error;
	} cb;
	uint64_t reply;
	int error, cmd_type;

	/* Allocate DMA-coherent buffer for memcfg (8-byte aligned) */
	error = bus_dma_tag_create(sc->sc_dma_tag,
	    8, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR,
	    NULL, NULL, sizeof(*cfg), 1, sizeof(*cfg),
	    BUS_DMA_WAITOK, NULL, NULL, &tag);
	if (error != 0)
		return (error);

	error = bus_dmamem_alloc(tag, (void **)&dma_cfg,
	    BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &map);
	if (error != 0) {
		bus_dma_tag_destroy(tag);
		return (error);
	}

	/* Copy config and load DMA address */
	memcpy(dma_cfg, cfg, sizeof(*cfg));

	cb.error = 0;
	error = bus_dmamap_load(tag, map, dma_cfg, sizeof(*cfg),
	    bce_reg_dma_cb, &cb, BUS_DMA_WAITOK);
	if (error != 0 || cb.error != 0) {
		bus_dmamem_free(tag, dma_cfg, map);
		bus_dma_tag_destroy(tag);
		return (error != 0 ? error : cb.error);
	}
	paddr = cb.addr;

	/* Sync DMA buffer before device access */
	bus_dmamap_sync(tag, map, BUS_DMASYNC_PREWRITE);

	/* Send DMA address of memcfg to firmware */
	cmd_type = is_sq ? BCE_MB_REGISTER_CMD_SQ : BCE_MB_REGISTER_CMD_CQ;
	error = bce_mailbox_send(&sc->sc_mbox,
	    BCE_MB_MSG(cmd_type, paddr), &reply, BCE_MBOX_TIMEOUT_MS);

	bus_dmamap_sync(tag, map, BUS_DMASYNC_POSTWRITE);
	bus_dmamap_unload(tag, map);
	bus_dmamem_free(tag, dma_cfg, map);
	bus_dma_tag_destroy(tag);

	if (error != 0)
		return (error);

	if (BCE_MB_TYPE(reply) != BCE_MB_REGISTER_QUEUE_REPLY) {
		device_printf(sc->sc_dev,
		    "unexpected queue registration reply: type=%u\n",
		    BCE_MB_TYPE(reply));
		return (EINVAL);
	}

	return (0);
}

/*
 * Setup command queues (CQ qid=0, SQ qid=1).
 */
static int
apple_bce_setup_cmd_queues(struct apple_bce_softc *sc)
{
	struct bce_queue_memcfg cfg;
	struct bce_queue_sq *sq;
	int error;

	/* Allocate command CQ (qid 0, 32 entries) */
	sc->sc_cmd_cq = bce_alloc_cq(sc, 0, 32);
	if (sc->sc_cmd_cq == NULL) {
		device_printf(sc->sc_dev, "failed to allocate command CQ\n");
		return (ENOMEM);
	}

	/* Register CQ with firmware via DMA-mapped memcfg */
	bce_get_cq_memcfg(sc->sc_cmd_cq, &cfg);
	error = apple_bce_register_cmd_queue(sc, &cfg, 0);
	if (error != 0) {
		device_printf(sc->sc_dev, "failed to register command CQ\n");
		goto fail_cq;
	}

	/* Store CQ in queue registries */
	mtx_lock(&sc->sc_queues_lock);
	sc->sc_queues[0] = sc->sc_cmd_cq;
	sc->sc_cq_list[0] = sc->sc_cmd_cq;
	mtx_unlock(&sc->sc_queues_lock);

	/* Allocate command SQ (qid 1, 64-byte elements, 32 entries) */
	sq = bce_alloc_sq(sc, 1, BCE_CMD_SIZE, 32, NULL, NULL);
	if (sq == NULL) {
		device_printf(sc->sc_dev, "failed to allocate command SQ\n");
		goto fail_cq;
	}

	/* Wrap SQ in command queue (sets completion callback internally) */
	sc->sc_cmd_cmdq = bce_alloc_cmdq(sc, sq);
	if (sc->sc_cmd_cmdq == NULL) {
		bce_free_sq(sc, sq);
		goto fail_cq;
	}

	/* Register SQ with firmware via DMA-mapped memcfg */
	bce_get_sq_memcfg(sq, sc->sc_cmd_cq, &cfg);
	error = apple_bce_register_cmd_queue(sc, &cfg, 1);
	if (error != 0) {
		device_printf(sc->sc_dev, "failed to register command SQ\n");
		goto fail_sq;
	}

	/* Store SQ in queue registry */
	mtx_lock(&sc->sc_queues_lock);
	sc->sc_queues[1] = sq;
	sc->sc_int_sq_list[0] = sq;
	mtx_unlock(&sc->sc_queues_lock);

	device_printf(sc->sc_dev, "command queues created (CQ=0, SQ=1)\n");
	return (0);

fail_sq:
	bce_free_cmdq(sc->sc_cmd_cmdq);
	sc->sc_cmd_cmdq = NULL;
	bce_free_sq(sc, sq);
fail_cq:
	/* Clear CQ from registries before freeing */
	mtx_lock(&sc->sc_queues_lock);
	sc->sc_queues[0] = NULL;
	sc->sc_cq_list[0] = NULL;
	mtx_unlock(&sc->sc_queues_lock);
	bce_free_cq(sc, sc->sc_cmd_cq);
	sc->sc_cmd_cq = NULL;
	return (error != 0 ? error : ENOMEM);
}

/*
 * PCI probe.
 */
static int
apple_bce_probe(device_t dev)
{
	if (pci_get_vendor(dev) != BCE_PCI_VENDOR_APPLE ||
	    pci_get_device(dev) != BCE_PCI_DEVICE_T2)
		return (ENXIO);

	/* Only attach to function 1 (BCE), not function 0 (NVMe) */
	if (pci_get_function(dev) != 1)
		return (ENXIO);

	device_set_desc(dev, "Apple T2 Buffer Copy Engine");
	return (BUS_PROBE_DEFAULT);
}

/*
 * PCI attach.
 */
static int
apple_bce_attach(device_t dev)
{
	struct apple_bce_softc *sc = device_get_softc(dev);
	int error;

	sc->sc_dev = dev;
	pci_enable_busmaster(dev);
	mtx_init(&sc->sc_queues_lock, "bce_queues", NULL, MTX_DEF);
	mtx_init(&sc->sc_timestamp_lock, "bce_ts", NULL, MTX_SPIN);
	callout_init(&sc->sc_timestamp_co, 1);

	/* Map BAR2 (DMA) and BAR4 (mailbox) */
	sc->sc_bar2_rid = PCIR_BAR(2);
	sc->sc_bar2 = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
	    &sc->sc_bar2_rid, RF_ACTIVE);
	if (sc->sc_bar2 == NULL) {
		device_printf(dev, "cannot map BAR2 (DMA)\n");
		error = ENXIO;
		goto fail;
	}

	sc->sc_bar4_rid = PCIR_BAR(4);
	sc->sc_bar4 = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
	    &sc->sc_bar4_rid, RF_ACTIVE);
	if (sc->sc_bar4 == NULL) {
		device_printf(dev, "cannot map BAR4 (mailbox)\n");
		error = ENXIO;
		goto fail;
	}

	/* Allocate MSI vectors (need at least 5: mbox=0, dma=4) */
	sc->sc_msi_count = 8;	/* Must be power of 2 for FreeBSD */
	error = pci_alloc_msi(dev, &sc->sc_msi_count);
	if (error != 0 || sc->sc_msi_count < 8) {
		device_printf(dev, "cannot allocate MSI vectors: "
		    "error=%d got=%d\n", error, sc->sc_msi_count);
		if (error == 0 && sc->sc_msi_count > 0)
			pci_release_msi(dev);
		sc->sc_msi_count = 0;
		error = ENXIO;
		goto fail;
	}

	/* Initialize mailbox before installing handlers */
	bce_mailbox_init(&sc->sc_mbox, sc->sc_bar4);

	/* Setup IRQ: vector 0 = mailbox, vector 4 = DMA */
	sc->sc_irq_rid_mbox = 1;	/* MSI vectors start at rid 1 */
	sc->sc_irq_mbox = bus_alloc_resource_any(dev, SYS_RES_IRQ,
	    &sc->sc_irq_rid_mbox, RF_ACTIVE);
	if (sc->sc_irq_mbox == NULL) {
		device_printf(dev, "cannot allocate mailbox IRQ\n");
		error = ENXIO;
		goto fail;
	}

	error = bus_setup_intr(dev, sc->sc_irq_mbox,
	    INTR_TYPE_MISC | INTR_MPSAFE,
	    NULL, apple_bce_mbox_intr, sc, &sc->sc_irq_mbox_cookie);
	if (error != 0) {
		device_printf(dev, "cannot setup mailbox IRQ\n");
		goto fail;
	}

	sc->sc_irq_rid_dma = 5;	/* Vector 4 = rid 5 */
	sc->sc_irq_dma = bus_alloc_resource_any(dev, SYS_RES_IRQ,
	    &sc->sc_irq_rid_dma, RF_ACTIVE);
	if (sc->sc_irq_dma == NULL) {
		device_printf(dev, "cannot allocate DMA IRQ\n");
		error = ENXIO;
		goto fail;
	}

	error = bus_setup_intr(dev, sc->sc_irq_dma,
	    INTR_TYPE_MISC | INTR_MPSAFE,
	    NULL, apple_bce_dma_intr, sc, &sc->sc_irq_dma_cookie);
	if (error != 0) {
		device_printf(dev, "cannot setup DMA IRQ\n");
		goto fail;
	}

	/* Create parent DMA tag with 37-bit addressing limit */
	error = bus_dma_tag_create(bus_get_dma_tag(dev),
	    1, 0,			/* alignment, boundary */
	    (1ULL << 37) - 1,		/* lowaddr: 37-bit limit */
	    BUS_SPACE_MAXADDR,		/* highaddr */
	    NULL, NULL,			/* filter */
	    BUS_SPACE_MAXSIZE,		/* maxsize */
	    BUS_SPACE_UNRESTRICTED,	/* nsegments */
	    BUS_SPACE_MAXSIZE,		/* maxsegsize */
	    0,				/* flags */
	    NULL, NULL,			/* lockfunc */
	    &sc->sc_dma_tag);
	if (error != 0) {
		device_printf(dev, "cannot create DMA tag\n");
		goto fail;
	}

	/*
	 * Enable bus master on function 0 (NVMe) -- T2 quirk.
	 * Must be done before firmware handshake; T2 rejects DMA
	 * on function 1 unless function 0 is bus master.
	 */
	apple_bce_enable_pci0_busmaster(dev);

	/* Initialize and start timestamp keepalive */
	apple_bce_timestamp_init(sc);
	apple_bce_timestamp_start(sc, 1);

	/* Firmware handshake */
	error = apple_bce_fw_handshake(sc);
	if (error != 0)
		goto fail;

	/* Setup command queues */
	error = apple_bce_setup_cmd_queues(sc);
	if (error != 0)
		goto fail;

	device_printf(dev, "Apple T2 BCE initialized\n");

	/* Create VHCI child for virtual USB */
	error = bce_vhci_attach(sc);
	if (error != 0)
		goto fail;

	return (0);

fail:
	apple_bce_detach(dev);
	return (error);
}

/*
 * PCI detach.
 */
static int
apple_bce_detach(device_t dev)
{
	struct apple_bce_softc *sc = device_get_softc(dev);

	/* 0. Detach VHCI child first (before destroying parent resources) */
	bce_vhci_detach(sc);

	/* 1. Stop timestamp */
	if (sc->sc_bar4 != NULL && mtx_initialized(&sc->sc_timestamp_lock))
		apple_bce_timestamp_stop(sc);
	else
		callout_drain(&sc->sc_timestamp_co);

	/* 2. Tear down IRQs first -- no more interrupts after this */
	if (sc->sc_irq_dma_cookie != NULL) {
		bus_teardown_intr(dev, sc->sc_irq_dma, sc->sc_irq_dma_cookie);
		sc->sc_irq_dma_cookie = NULL;
	}
	if (sc->sc_irq_mbox_cookie != NULL) {
		bus_teardown_intr(dev, sc->sc_irq_mbox,
		    sc->sc_irq_mbox_cookie);
		sc->sc_irq_mbox_cookie = NULL;
	}

	/* 3. Free command queues (safe now -- no IRQs can fire) */
	if (sc->sc_cmd_cmdq != NULL) {
		struct bce_queue_sq *cmd_sq;

		cmd_sq = sc->sc_cmd_cmdq->sq;
		bce_free_cmdq(sc->sc_cmd_cmdq);
		sc->sc_cmd_cmdq = NULL;
		bce_free_sq(sc, cmd_sq);
	}
	if (sc->sc_cmd_cq != NULL) {
		bce_free_cq(sc, sc->sc_cmd_cq);
		sc->sc_cmd_cq = NULL;
	}

	/* 4. Clear queue registries */
	if (mtx_initialized(&sc->sc_queues_lock)) {
		mtx_lock(&sc->sc_queues_lock);
		memset(sc->sc_queues, 0, sizeof(sc->sc_queues));
		memset(sc->sc_cq_list, 0, sizeof(sc->sc_cq_list));
		memset(sc->sc_int_sq_list, 0, sizeof(sc->sc_int_sq_list));
		mtx_unlock(&sc->sc_queues_lock);
	}

	/* 5. Destroy mailbox */
	bce_mailbox_destroy(&sc->sc_mbox);

	/* 6. Release DMA tag */
	if (sc->sc_dma_tag != NULL) {
		bus_dma_tag_destroy(sc->sc_dma_tag);
		sc->sc_dma_tag = NULL;
	}

	/* 7. Release IRQ resources */
	if (sc->sc_irq_dma != NULL) {
		bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid_dma,
		    sc->sc_irq_dma);
		sc->sc_irq_dma = NULL;
	}
	if (sc->sc_irq_mbox != NULL) {
		bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid_mbox,
		    sc->sc_irq_mbox);
		sc->sc_irq_mbox = NULL;
	}
	if (sc->sc_msi_count > 0) {
		pci_release_msi(dev);
		sc->sc_msi_count = 0;
	}

	/* 8. Release BARs */
	if (sc->sc_bar4 != NULL) {
		bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_bar4_rid,
		    sc->sc_bar4);
		sc->sc_bar4 = NULL;
	}
	if (sc->sc_bar2 != NULL) {
		bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_bar2_rid,
		    sc->sc_bar2);
		sc->sc_bar2 = NULL;
	}

	if (mtx_initialized(&sc->sc_timestamp_lock))
		mtx_destroy(&sc->sc_timestamp_lock);
	if (mtx_initialized(&sc->sc_queues_lock))
		mtx_destroy(&sc->sc_queues_lock);

	return (0);
}

static int
apple_bce_suspend(device_t dev)
{
	struct apple_bce_softc *sc = device_get_softc(dev);
	int error, restore_error;

	apple_bce_timestamp_stop(sc);

	error = bce_vhci_detach(sc);
	if (error != 0) {
		device_printf(dev, "failed to detach VHCI for suspend: %d\n",
		    error);
		apple_bce_timestamp_start(sc, 0);
		return (error);
	}

	error = bce_mailbox_send(&sc->sc_mbox,
	    BCE_MB_MSG(BCE_MB_SLEEP_NO_STATE, 0), NULL,
	    BCE_MBOX_TIMEOUT_MS);
	if (error != 0) {
		device_printf(dev,
		    "failed to send SLEEP_NO_STATE mailbox command: %d\n",
		    error);
		restore_error = bce_vhci_attach(sc);
		if (restore_error != 0) {
			device_printf(dev,
			    "failed to reattach VHCI after suspend error: %d\n",
			    restore_error);
		}
		apple_bce_timestamp_start(sc, 0);
		return (error);
	}

	return (0);
}

static int
apple_bce_resume(device_t dev)
{
	struct apple_bce_softc *sc = device_get_softc(dev);
	uint64_t reply;
	int error;

	error = bce_mailbox_send(&sc->sc_mbox,
	    BCE_MB_MSG(BCE_MB_RESTORE_NO_STATE, 0), &reply,
	    BCE_MBOX_TIMEOUT_MS);
	if (error != 0) {
		device_printf(dev,
		    "failed to send RESTORE_NO_STATE mailbox command: %d\n",
		    error);
		return (error);
	}

	if (BCE_MB_TYPE(reply) != BCE_MB_RESTORE_NO_STATE) {
		device_printf(dev,
		    "unexpected RESTORE_NO_STATE reply: type=%u val=0x%llx\n",
		    BCE_MB_TYPE(reply),
		    (unsigned long long)BCE_MB_VALUE(reply));
		return (EINVAL);
	}

	error = bce_vhci_attach(sc);
	if (error != 0) {
		device_printf(dev, "failed to reattach VHCI after resume: %d\n",
		    error);
		apple_bce_timestamp_start(sc, 0);
		return (error);
	}

	apple_bce_timestamp_start(sc, 0);
	return (0);
}

static device_method_t apple_bce_methods[] = {
	DEVMETHOD(device_probe,		apple_bce_probe),
	DEVMETHOD(device_attach,	apple_bce_attach),
	DEVMETHOD(device_detach,	apple_bce_detach),
	DEVMETHOD(device_suspend,	apple_bce_suspend),
	DEVMETHOD(device_resume,	apple_bce_resume),
	DEVMETHOD_END
};

static driver_t apple_bce_driver = {
	"apple_bce",
	apple_bce_methods,
	sizeof(struct apple_bce_softc)
};

DRIVER_MODULE(apple_bce, pci, apple_bce_driver, NULL, NULL);
MODULE_DEPEND(apple_bce, pci, 1, 1, 1);
static const struct {
	uint16_t	vendor;
	uint16_t	device;
} apple_bce_pnp[] = {
	{ BCE_PCI_VENDOR_APPLE, BCE_PCI_DEVICE_T2 },
};

MODULE_PNP_INFO("U16:vendor;U16:device", pci, apple_bce, apple_bce_pnp,
    nitems(apple_bce_pnp));
