/*	$NetBSD: psgpam.c,v 1.2 2022/06/11 14:45:37 tsutsui Exp $	*/

/*
 * Copyright (c) 2018 Yosuke Sugahara. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: psgpam.c,v 1.2 2022/06/11 14:45:37 tsutsui Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/endian.h>
#include <sys/kmem.h>
#include <sys/sysctl.h>

#include <sys/cpu.h>
#include <sys/audioio.h>
#include <dev/audio/audio_if.h>

#include <machine/autoconf.h>

#include <luna68k/dev/xpbusvar.h>
#include <luna68k/dev/psgpam_enc.h>
#include <luna68k/dev/xpcmd.h>
#include <luna68k/dev/xplx/xplxdefs.h>

#include <luna68k/luna68k/isr.h>

#include "ioconf.h"

/*
 * Debug level:
 * 0: No debug logs
 * 1: action changes like open/close/set_format...
 * 2: + normal operations like read/write/ioctl...
 * 3: + TRACEs except interrupt
 * 4: + TRACEs including interrupt
 */
/* Note AUDIO_DEBUG should be sync'ed with src/sys/dev/audio/audio.c */
/* #define AUDIO_DEBUG	1 */

#if defined(AUDIO_DEBUG)
#define DPRINTF(n, fmt...)	do {					\
	if (psgpamdebug >= (n)) {					\
		if (cpu_intr_p()) {					\
			audio_mlog_printf(fmt);				\
		} else {						\
			audio_mlog_flush();				\
			printf(fmt);					\
		}							\
	}								\
} while (0)

/* XXX Parasitic to audio.c... */
extern void audio_mlog_flush(void);
extern void audio_mlog_printf(const char *, ...);

static int	psgpamdebug = AUDIO_DEBUG;
#else
#define DPRINTF(n, fmt...)	__nothing
#endif

struct psgpam_softc {
	device_t sc_dev;
	vaddr_t sc_shm_base;
	vsize_t sc_shm_size;

	void (*sc_intr)(void *);
	void *sc_arg;

	kmutex_t sc_intr_lock;
	kmutex_t sc_thread_lock;

	int      sc_isopen;

	int      sc_started;
	int      sc_outcount;
	int      sc_xp_state;
	uint16_t sc_xp_addr;	/* XP buffer addr */

	int      sc_xp_enc;
	int      sc_xp_rept;
	int      sc_xp_cycle_clk;
	int      sc_xp_rept_clk;
	int      sc_xp_rept_max;

	u_int    sc_sample_rate;
	int      sc_stride;
	int      sc_dynamic;

	uint8_t *sc_start_ptr;
	uint8_t *sc_end_ptr;
	int      sc_blksize;
	int      sc_blkcount;
	int      sc_cur_blk_id;

	struct psgpam_codecvar sc_psgpam_codecvar;
};

static int  psgpam_match(device_t, cfdata_t, void *);
static void psgpam_attach(device_t, device_t, void *);

/* MI audio layer interface */
static int  psgpam_open(void *, int);
static void psgpam_close(void *);
static int  psgpam_query_format(void *, audio_format_query_t *);
static int  psgpam_set_format(void *, int,
    const audio_params_t *, const audio_params_t *,
    audio_filter_reg_t *, audio_filter_reg_t *);
static int  psgpam_trigger_output(void *, void *, void *, int,
    void (*)(void *), void *, const audio_params_t *);
static int  psgpam_halt_output(void *);
static int  psgpam_getdev(void *, struct audio_device *);
static int  psgpam_set_port(void *, mixer_ctrl_t *);
static int  psgpam_get_port(void *, mixer_ctrl_t *);
static int  psgpam_query_devinfo(void *, mixer_devinfo_t *);
static int  psgpam_get_props(void *);
static void psgpam_get_locks(void *, kmutex_t **, kmutex_t **);
static int  psgpam_round_blocksize(void *, int, int, const audio_params_t *);
static size_t psgpam_round_buffersize(void *, int, size_t);

static int  psgpam_intr(void *);

#if defined(AUDIO_DEBUG)
static int  psgpam_sysctl_debug(SYSCTLFN_PROTO);
#endif
static int  psgpam_sysctl_enc(SYSCTLFN_PROTO);
static int  psgpam_sysctl_dynamic(SYSCTLFN_PROTO);

CFATTACH_DECL_NEW(psgpam, sizeof(struct psgpam_softc),
    psgpam_match, psgpam_attach, NULL, NULL);

static int psgpam_matched;

static const struct audio_hw_if psgpam_hw_if = {
	.open			= psgpam_open,
	.close			= psgpam_close,
	.query_format		= psgpam_query_format,
	.set_format		= psgpam_set_format,
	.trigger_output		= psgpam_trigger_output,
	.halt_output		= psgpam_halt_output,
	.getdev			= psgpam_getdev,
	.set_port		= psgpam_set_port,
	.get_port		= psgpam_get_port,
	.query_devinfo		= psgpam_query_devinfo,
	.get_props		= psgpam_get_props,
	.get_locks		= psgpam_get_locks,
	.round_blocksize        = psgpam_round_blocksize,
	.round_buffersize	= psgpam_round_buffersize,
};

static struct audio_device psgpam_device = {
	"PSG PAM",
	"0.2",
	"",
};

static struct audio_format psgpam_format = {
	.mode		= AUMODE_PLAY,
	.encoding	= AUDIO_ENCODING_NONE,
	.validbits	= 0,		/* filled by query_format */
	.precision	= 0,		/* filled by query_format */
	.channels	= 1,
	.channel_mask	= AUFMT_MONAURAL,
	.frequency_type	= 0,		/* filled by query_format */
	.frequency	= { 0 },	/* filled by query_format */
};

static int
psgpam_match(device_t parent, cfdata_t cf, void *aux)
{
	struct xpbus_attach_args *xa = aux;

	if (psgpam_matched)
		return 0;

	/* Only the first generation LUNA has YM2149 at XP */
	if (machtype != LUNA_I)
		return 0;

	if (strcmp(xa->xa_name, psgpam_cd.cd_name))
		return 0;

	psgpam_matched = 1;
	return 1;
}

static void
psgpam_attach(device_t parent, device_t self, void *aux)
{
	struct psgpam_softc *sc;
	const struct sysctlnode *node;

	sc = device_private(self);
	sc->sc_dev = self;

	aprint_normal(": HD647180X I/O processor as PSG PAM\n");

	sc->sc_shm_base = XP_SHM_BASE;
	sc->sc_shm_size = XP_SHM_SIZE;

	sc->sc_xp_enc = PAM_ENC_PAM2A;
	sc->sc_sample_rate = 8000;
	sc->sc_stride = 2;
	sc->sc_dynamic = 1;

	mutex_init(&sc->sc_thread_lock, MUTEX_DEFAULT, IPL_NONE);
	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);

	isrlink_autovec(psgpam_intr, sc, 5, ISRPRI_TTYNOBUF);

	sysctl_createv(NULL, 0, NULL, &node,
		0,
		CTLTYPE_NODE, device_xname(sc->sc_dev),
		SYSCTL_DESCR("psgpam"),
		NULL, 0,
		NULL, 0,
		CTL_HW,
		CTL_CREATE, CTL_EOL);
	if (node != NULL) {
#if defined(AUDIO_DEBUG)
		sysctl_createv(NULL, 0, NULL, NULL,
			CTLFLAG_READWRITE,
			CTLTYPE_INT, "debug",
			SYSCTL_DESCR("PSGPAM debug"),
			psgpam_sysctl_debug, 0, (void *)sc, 0,
			CTL_HW, node->sysctl_num,
			CTL_CREATE, CTL_EOL);
#endif
		sysctl_createv(NULL, 0, NULL, NULL,
			CTLFLAG_READWRITE,
			CTLTYPE_INT, "enc",
			SYSCTL_DESCR("PSGPAM encoding"),
			psgpam_sysctl_enc, 0, (void *)sc, 0,
			CTL_HW, node->sysctl_num,
			CTL_CREATE, CTL_EOL);
		sysctl_createv(NULL, 0, NULL, NULL,
			CTLFLAG_READWRITE,
			CTLTYPE_INT, "dynamic",
			SYSCTL_DESCR("PSGPAM dynamic offset"),
			psgpam_sysctl_dynamic, 0, (void *)sc, 0,
			CTL_HW, node->sysctl_num,
			CTL_CREATE, CTL_EOL);
	}

	audio_attach_mi(&psgpam_hw_if, sc, sc->sc_dev);
}

/* private functions */

static void
psgpam_xp_query(struct psgpam_softc *sc)
{
	u_int a;
	int r;

	if (!sc->sc_isopen) {
		a = xp_acquire(DEVID_PAM, 0);
		if (a == 0) {
			sc->sc_xp_cycle_clk = 65535;
			sc->sc_xp_rept_clk = 255;
			sc->sc_xp_rept_max = 0;
			DPRINTF(1, "XPLX BUSY!\n");
			return;
		}
		xp_ensure_firmware();
	}

	xp_writemem8(PAM_ENC, sc->sc_xp_enc);
	r = xp_cmd(DEVID_PAM, PAM_CMD_QUERY);
	if (r != XPLX_R_OK) {
		sc->sc_xp_cycle_clk = 65535;
		sc->sc_xp_rept_clk = 255;
		sc->sc_xp_rept_max = 0;
		DPRINTF(1, "XPLX QUERY FAIL: %d\n", r);
	} else {
		sc->sc_xp_cycle_clk = xp_readmem16le(PAM_CYCLE_CLK);
		sc->sc_xp_rept_clk = xp_readmem8(PAM_REPT_CLK);
		sc->sc_xp_rept_max = xp_readmem8(PAM_REPT_MAX);
		DPRINTF(1, "xp cycle_clk=%d rept_clk=%d rept_max=%d\n",
		    sc->sc_xp_cycle_clk,
		    sc->sc_xp_rept_clk,
		    sc->sc_xp_rept_max);
	}
	if (!sc->sc_isopen) {
		xp_release(DEVID_PAM);
	}
}

static void
psgpam_xp_start(struct psgpam_softc *sc)
{

	DPRINTF(3, "XP PAM starting..");
	if (xp_readmem8(PAM_RUN) != 0) {
		DPRINTF(1, "XP PAM already started???\n");
	}

	psgpam_xp_query(sc);

	sc->sc_xp_rept = (XP_CPU_FREQ / sc->sc_sample_rate
	    - sc->sc_xp_cycle_clk) / sc->sc_xp_rept_clk;
	if (sc->sc_xp_rept < 0)
		sc->sc_xp_rept = 0;
	if (sc->sc_xp_rept > sc->sc_xp_rept_max)
		sc->sc_xp_rept = sc->sc_xp_rept_max;
	xp_writemem8(PAM_REPT, sc->sc_xp_rept);
	DPRINTF(3, "ENC=%d REPT=%d\n", sc->sc_xp_enc, sc->sc_xp_rept);

	xp_intr5_enable();
	xp_cmd_nowait(DEVID_PAM, PAM_CMD_START);

	DPRINTF(3, "XP PAM started\n");
}

/* MI MD API */

static int
psgpam_open(void *hdl, int flags)
{
	struct psgpam_softc *sc;
	u_int a;

	DPRINTF(1, "%s: flags=0x%x\n", __func__, flags);
	sc = hdl;

	a = xp_acquire(DEVID_PAM, 0);
	if (a == 0)
		return EBUSY;

	/* firmware transfer */
	xp_ensure_firmware();

	sc->sc_xp_state = 0;
	sc->sc_started = 0;
	sc->sc_outcount = 0;
	sc->sc_isopen = 1;

	memset(xp_shmptr(PAM_BUF), XP_ATN_RESET, PAM_BUF_LEN);

	return 0;
}

static void
psgpam_close(void *hdl)
{
	struct psgpam_softc *sc;

	sc = hdl;

	xp_intr5_disable();

	xp_release(DEVID_PAM);

	sc->sc_isopen = 0;

	DPRINTF(1, "%s\n", __func__);
}

static int
psgpam_query_format(void *hdl, audio_format_query_t *afp)
{
	struct psgpam_softc *sc;
	u_int freq;
	uint8_t rept_max;
	int clk;
	int i, n;

#define XP_FREQ_MAXCOUNT	40
	int f[XP_FREQ_MAXCOUNT];

	if (afp->index != 0)
		return EINVAL;

	sc = hdl;

	psgpam_xp_query(sc);
	switch (sc->sc_xp_enc) {
	case PAM_ENC_PAM2A:
	case PAM_ENC_PAM2B:
		psgpam_format.validbits = 16;
		psgpam_format.precision = 16;
		break;
	case PAM_ENC_PAM3A:
	case PAM_ENC_PAM3B:
		psgpam_format.validbits = 32;
		psgpam_format.precision = 32;
		break;
	}

	/* convert xp's max to AUFMT's max */
	rept_max = sc->sc_xp_rept_max + 1;

	if (rept_max <= AUFMT_MAX_FREQUENCIES) {
		/* all choice */
		for (i = 0; i < rept_max; i++) {
			clk = sc->sc_xp_cycle_clk + i * sc->sc_xp_rept_clk;
			freq = XP_CPU_FREQ / clk;
			psgpam_format.frequency[i] = freq;
		}
		n = rept_max;
	} else {
		if (rept_max > XP_FREQ_MAXCOUNT)
			rept_max = XP_FREQ_MAXCOUNT;

		for (i = 0; i < rept_max; i++) {
			clk = sc->sc_xp_cycle_clk + i * sc->sc_xp_rept_clk;
			freq = XP_CPU_FREQ / clk;
			if (freq < 4000) break;
			f[i] = freq;
		}
		for (; i < XP_FREQ_MAXCOUNT; i++)
			f[i] = 0;

		/*
		 * keep: first, last
		 * remove: any unusable freq
		 */
		for (i = 1; i < rept_max - 1; i++) {
			if (( 4000 <= f[i] && f[i] <  6000 &&
			     f[i - 1] <  6000 && f[i + 1] >  4000) ||
			    ( 6000 <= f[i]    && f[i] < 8000 &&
			     f[i - 1] <  8000 && f[i + 1] >  6000) ||
			    ( 8000 <= f[i] && f[i] < 12000 &&
			     f[i - 1] < 12000 && f[i + 1] >  8000) ||
			    (12000 <= f[i] && f[i] < 16000 &&
			     f[i - 1] < 16000 && f[i + 1] > 12000)) {
				f[i] = 0;
			}
		}
		n = 0;
		for (i = 0; i < rept_max; i++) {
			if (f[i] != 0) {
				psgpam_format.frequency[n] = f[i];
				n++;
				if (n == AUFMT_MAX_FREQUENCIES)
					break;
			}
		}
	}

	psgpam_format.frequency_type = n;

	afp->fmt = psgpam_format;
	return 0;
}

static int
psgpam_set_format(void *hdl, int setmode,
    const audio_params_t *play, const audio_params_t *rec,
    audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
{
	/* called before open */

	struct psgpam_softc *sc;

	sc = hdl;
	DPRINTF(1, "%s: mode=%d %s/%dbit/%dch/%dHz\n", __func__,
	    setmode, audio_encoding_name(play->encoding),
	    play->precision, play->channels, play->sample_rate);

	sc->sc_sample_rate = play->sample_rate;

	/* set filter */
	switch (sc->sc_xp_enc) {
	case PAM_ENC_PAM2A:
		if (sc->sc_dynamic) {
			pfil->codec = psgpam_aint_to_pam2a_d;
		} else {
			pfil->codec = psgpam_aint_to_pam2a;
		}
		sc->sc_stride = 2;
		break;
	case PAM_ENC_PAM2B:
		if (sc->sc_dynamic) {
			pfil->codec = psgpam_aint_to_pam2b_d;
		} else {
			pfil->codec = psgpam_aint_to_pam2b;
		}
		sc->sc_stride = 2;
		break;
	case PAM_ENC_PAM3A:
		if (sc->sc_dynamic) {
			pfil->codec = psgpam_aint_to_pam3a_d;
		} else {
			pfil->codec = psgpam_aint_to_pam3a;
		}
		sc->sc_stride = 4;
		break;
	case PAM_ENC_PAM3B:
		if (sc->sc_dynamic) {
			pfil->codec = psgpam_aint_to_pam3b_d;
		} else {
			pfil->codec = psgpam_aint_to_pam3b;
		}
		sc->sc_stride = 4;
		break;
	}
	psgpam_init_context(&sc->sc_psgpam_codecvar, sc->sc_sample_rate);
	pfil->context = &sc->sc_psgpam_codecvar;

	return 0;
}

/* marking block */
static void
psgpam_mark_blk(struct psgpam_softc *sc, int blk_id)
{
	int markoffset;
	uint marker;

	markoffset = sc->sc_blksize * (blk_id + 1);

	if (blk_id == sc->sc_blkcount - 1) {
		marker = XP_ATN_RELOAD;
	} else {
		marker = XP_ATN_STAT;
	}

	/* marking */
	uint8_t *start = sc->sc_start_ptr;
	if (sc->sc_stride == 2) {
		uint16_t *markptr = (uint16_t*)(start + markoffset);
		markptr -= 1;
		*markptr |= marker;
	} else {
		/* stride == 4 */
		uint32_t *markptr = (uint32_t*)(start + markoffset);
		markptr -= 1;
		*markptr |= marker;
	}
}

static int
psgpam_trigger_output(void *hdl, void *start, void *end, int blksize,
    void (*intr)(void *), void *intrarg, const audio_params_t *param)
{
	void *dp;
	struct psgpam_softc *sc;

	sc = hdl;

	DPRINTF(2, "%s start=%p end=%p blksize=%d\n", __func__,
		start, end, blksize);

	sc->sc_outcount++;

	sc->sc_intr = intr;
	sc->sc_arg = intrarg;
	sc->sc_blksize = blksize;

	sc->sc_start_ptr = start;
	sc->sc_end_ptr = end;
	sc->sc_blkcount = (sc->sc_end_ptr - sc->sc_start_ptr) / sc->sc_blksize;
	sc->sc_xp_addr = PAM_BUF;

	psgpam_mark_blk(sc, 0);
	psgpam_mark_blk(sc, 1);

	/* transfer */
	dp = xp_shmptr(sc->sc_xp_addr);
	memcpy(dp, start, blksize * 2);

	/* (preincrement variable in intr) */
	sc->sc_cur_blk_id = 1;
	sc->sc_xp_addr += blksize;

	/* play start */
	if (sc->sc_started == 0) {
		/* set flag first */
		sc->sc_started = 1;
		psgpam_xp_start(sc);
	}

	return 0;
}

static int
psgpam_halt_output(void *hdl)
{
	struct psgpam_softc *sc = hdl;

	DPRINTF(2, "%s\n", __func__);

	xp_intr5_disable();

	memset(xp_shmptr(PAM_BUF), XP_ATN_RESET, PAM_BUF_LEN);

	sc->sc_started = 0;
	sc->sc_xp_state = 0;

	return 0;
}

static int
psgpam_getdev(void *hdl, struct audio_device *ret)
{

	*ret = psgpam_device;
	return 0;
}

static int
psgpam_set_port(void *hdl, mixer_ctrl_t *mc)
{

	DPRINTF(2, "%s\n", __func__);
	return 0;
}

static int
psgpam_get_port(void *hdl, mixer_ctrl_t *mc)
{

	DPRINTF(2, "%s\n", __func__);
	return 0;
}

static int
psgpam_query_devinfo(void *hdl, mixer_devinfo_t *di)
{

	DPRINTF(2, "%s %d\n", __func__, di->index);
	switch (di->index) {
	default:
		return EINVAL;
	}
	return 0;
}

static int
psgpam_get_props(void *hdl)
{

	return AUDIO_PROP_PLAYBACK;
}

static void
psgpam_get_locks(void *hdl, kmutex_t **intr, kmutex_t **thread)
{
	struct psgpam_softc *sc = hdl;

	*intr = &sc->sc_intr_lock;
	*thread = &sc->sc_thread_lock;
}

static int
psgpam_round_blocksize(void *hdl, int bs, int mode,
    const audio_params_t *param)
{

#if 0
	if (bs < 16384) {
		return (16384 / bs) * bs;
	} else {
		return 16384;
	}
#else
	return bs;
#endif
}

static size_t
psgpam_round_buffersize(void *hdl, int direction, size_t bufsize)
{

	if (bufsize > 28 * 1024) {
		bufsize = 28 * 1024;
	}
	return bufsize;
}

static int
psgpam_intr(void *hdl)
{
	struct psgpam_softc *sc = hdl;

	xp_intr5_acknowledge();
	DPRINTF(4, "psgpam intr\n");

	sc->sc_cur_blk_id++;
	sc->sc_xp_addr += sc->sc_blksize;
	if (sc->sc_cur_blk_id == sc->sc_blkcount) {
		sc->sc_cur_blk_id = 0;
		sc->sc_xp_addr = PAM_BUF;
	}
	psgpam_mark_blk(sc, sc->sc_cur_blk_id);
	memcpy(xp_shmptr(sc->sc_xp_addr),
	    sc->sc_start_ptr + sc->sc_cur_blk_id * sc->sc_blksize,
	    sc->sc_blksize);

	mutex_spin_enter(&sc->sc_intr_lock);

	if (sc->sc_intr) {
		sc->sc_intr(sc->sc_arg);
	} else {
		DPRINTF(1, "psgpam_intr: spurious interrupt\n");
	}

	mutex_spin_exit(&sc->sc_intr_lock);

	/* handled */
	return 1;
}

#if defined(AUDIO_DEBUG)
/* sysctl */
static int
psgpam_sysctl_debug(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	int t, error;

	node = *rnode;

	t = psgpamdebug;
	node.sysctl_data = &t;

	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error || newp == NULL) {
		return error;
	}

	if (t < 0)
		return EINVAL;
	if (t > 4)
		return EINVAL;
	psgpamdebug = t;
	return 0;
}
#endif

/* sysctl */
static int
psgpam_sysctl_enc(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct psgpam_softc *sc;
	int t, error;

	node = *rnode;
	sc = node.sysctl_data;

	t = sc->sc_xp_enc;
	node.sysctl_data = &t;

	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error || newp == NULL) {
		return error;
	}

	if (t < PAM_ENC_PAM2A)
		return EINVAL;
	if (t > PAM_ENC_PAM3B)
		return EINVAL;
	sc->sc_xp_enc = t;
	return 0;
}

static int
psgpam_sysctl_dynamic(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct psgpam_softc *sc;
	int t, error;

	node = *rnode;
	sc = node.sysctl_data;

	t = sc->sc_dynamic;
	node.sysctl_data = &t;

	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error || newp == NULL) {
		return error;
	}

	sc->sc_dynamic = t;
	return 0;
}
