/*	$NetBSD: sc16is7xx.c,v 1.5 2025/12/13 05:40:16 andvar Exp $	*/

/*
 * Copyright (c) 2025 Brad Spencer <brad@anduin.eldar.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "opt_fdt.h"

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sc16is7xx.c,v 1.5 2025/12/13 05:40:16 andvar Exp $");

/* Common driver for the frontend to the NXP SC16IS7xx UART bridge */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/device.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/workqueue.h>

#ifdef FDT
#include <dev/fdt/fdtvar.h>
#endif

#include <dev/ic/sc16is7xxreg.h>
#include <dev/ic/sc16is7xxvar.h>
#include <dev/ic/sc16is7xx_ttyvar.h>

const struct device_compatible_entry sc16is7xx_compat_data[] = {
	{.compat = "nxp,sc16is740"},
	{.compat = "nxp,sc16is741"},
	{.compat = "nxp,sc16is750"},
	{.compat = "nxp,sc16is752"},
	{.compat = "nxp,sc16is760"},
	{.compat = "nxp,sc16is762"},

	DEVICE_COMPAT_EOL
};

void sc16is7xx_attach(struct sc16is7xx_sc *);
static int sc16is7xx_verify_poll(SYSCTLFN_ARGS);
static int sc16is7xx_verify_freq_sysctl(SYSCTLFN_ARGS);
void sc16is7xx_thread(void *);

/* Artificial interrupts and the like */

static void
sc16is7xx_comintr(struct sc16is7xx_sc *sc)
{
	struct sc16is7xx_tty_softc *csc;

	for (int i = 0; i <= 1; i++){
		if (sc->sc_ttydevchannel[i] != NULL) {
			csc = device_private(sc->sc_ttydevchannel[i]);
			if (csc != NULL)
				comintr(&csc->sc_com);
		}
	}
}

void
sc16is7xx_thread(void *arg)
{
	struct sc16is7xx_sc *sc = arg;

	while (sc->sc_thread_state != SC16IS7XX_THREAD_EXIT) {
		mutex_enter(&sc->sc_thread_mutex);
		if (sc->sc_thread_state == SC16IS7XX_THREAD_PAUSED ||
		    sc->sc_thread_state == SC16IS7XX_THREAD_STALLED ||
		    sc->sc_thread_state == SC16IS7XX_THREAD_STOPPED)
			cv_wait(&sc->sc_threadvar, &sc->sc_thread_mutex);
		mutex_exit(&sc->sc_thread_mutex);

		mutex_enter(&sc->sc_thread_mutex);
		if (sc->sc_thread_state == SC16IS7XX_THREAD_RUNNING) {
			mutex_exit(&sc->sc_thread_mutex);
			sc16is7xx_comintr(sc);
		} else {
			mutex_exit(&sc->sc_thread_mutex);
		}

		mutex_enter(&sc->sc_thread_mutex);
		if (sc->sc_thread_state == SC16IS7XX_THREAD_RUNNING)
			cv_timedwait(&sc->sc_threadvar, &sc->sc_thread_mutex,
			    mstohz(sc->sc_poll));
		mutex_exit(&sc->sc_thread_mutex);
		
	}
	kthread_exit(0);
}

#ifdef FDT
/* This song and dance is needed because:

   sc16is7xx_intr is entered in a hard interrupt context.  It is not
   allowed to call workqueue_enqueue() and can't call comintr()
   because that might need to talk to the I2C or SPI bus, allocate
   memory, or otherwise wait.

   sc16is7xx_softintr wasn't able to call comintr() directly as that
   resulted in a panic.

   Hence..  sc16is7xx_comintr() and then comintr() needed to be
   entered from a thread or a workqueue worker.
*/

static void
sc16is7xx_wq(struct work *wk, void *arg)
{
	struct sc16is7xx_sc *sc = arg;

	sc16is7xx_comintr(sc);
}

static void
sc16is7xx_softintr(void *arg)
{
	struct sc16is7xx_sc *sc = arg;

	/* This is a little strange,  See workqueue(9) about "A work must
	 * not be enqueued again until the callback is called by the
	 * workqueue framework."
	 *
	 * If one tries to use a variable from the stack here you will panic if
	 * there are a number of interrupts coming in quickly.  kmem_alloc()
	 * also panic'ed.  This is running in a interrupt context, although it
	 * is soft and kmem(9) didn't like that.
	 *
	 * There could be a lot of interrupts going on if the there is a lot to
	 * receive or transmit.
	 *
	 * It may be possible to get clever and send a couple of work items, one
	 * for each possible channel. */

	workqueue_enqueue(sc->sc_wq, (struct work *)&sc->sc_frequency, NULL);
}

static int
sc16is7xx_intr(void *arg)
{
	struct sc16is7xx_sc *sc = arg;

	softint_schedule(sc->sc_sih);

	return 1;
}
#endif

/* GPIO */

static uint32_t
sc16is7xx_to_gpio_flags(struct sc16is7xx_sc *sc, int pin, int nc,
    uint8_t iocontrol, uint8_t iodir)
{
	int f = 0;

	if (pin <= 3) {
		if (nc == 2) {
			if (iocontrol & SC16IS7XX_IOCONTROL_3_0)
				f = GPIO_PIN_ALT0;
		}
		if (f == 0) {
			if (iodir & (1 << pin))
				f = GPIO_PIN_OUTPUT;
			else
				f = GPIO_PIN_INPUT;
		}
	} else {
		if (iocontrol & SC16IS7XX_IOCONTROL_7_4)
			f = GPIO_PIN_ALT0;
		else if (iodir & (1 << pin))
			f = GPIO_PIN_OUTPUT;
		else
			f = GPIO_PIN_INPUT;
	}

	return f;
}

static int
sc16is7xx_gpio_pin_read(void *arg, int pin)
{
	struct sc16is7xx_sc *sc = arg;
	uint8_t r;
	int rr = GPIO_PIN_LOW, error;

	error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IOSTATE, 1, &r, 1);
	if (!error && (r & (1 << pin)))
		rr = GPIO_PIN_HIGH;

	return rr;
}

static void
sc16is7xx_gpio_pin_write(void *arg, int pin, int value)
{
	struct sc16is7xx_sc *sc = arg;
	uint8_t r;
	int error;

	error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IOSTATE, 1, &r, 1);
	if (!error) {
		if (value)
			r |= (1 << pin);
		else
			r &= ~(1 << pin);
		error = sc->sc_funcs->write_reg(sc, SC16IS7XX_REGISTER_IOSTATE, 1, &r, 1);
	}
}

static void
sc16is7xx_gpio_ctl_alt0(struct sc16is7xx_sc *sc, uint8_t bank_mask,
    int low_pin, int high_pin, uint32_t flags)
{
	int error;
	uint8_t iocontrol;

	error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 1, &iocontrol, 1);
	if (!error) {
		iocontrol |= bank_mask;
		error = sc->sc_funcs->write_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 1, &iocontrol, 1);
		if (!error) {
			for (int i = low_pin; i <= high_pin; i++)
				sc->sc_gpio_pins[i].pin_flags = flags;
		}
	}
}

static void
sc16is7xx_gpio_ctl_inout(struct sc16is7xx_sc *sc, uint8_t bank_mask,
    int low_pin, int high_pin, int a_pin, uint32_t flags)
{
	int error;
	uint8_t iocontrol, iodir;

	error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 1, &iocontrol, 1);
	if (!error) {
		iocontrol &= ~bank_mask;
		error = sc->sc_funcs->write_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 1, &iocontrol, 1);
		if (!error) {
			error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IODIR, 1, &iodir, 1);
			if (flags & GPIO_PIN_OUTPUT)
				iodir |= (1 << a_pin);
			if (flags & GPIO_PIN_INPUT)
				iodir &= ~(1 << a_pin);
			error = sc->sc_funcs->write_reg(sc, SC16IS7XX_REGISTER_IODIR, 1, &iodir, 1);
			if (!error) {
				for (int i = low_pin; i <= high_pin; i++)
					sc->sc_gpio_pins[i].pin_flags = sc16is7xx_to_gpio_flags(sc, i, sc->sc_num_channels, iocontrol, iodir);
			}
		}
	}
}

static void
sc16is7xx_gpio_pin_ctl(void *arg, int pin, int flags)
{
	struct sc16is7xx_sc *sc = arg;
	uint8_t bank_mask;
	int low_pin, high_pin;

	if (pin <= 3) {
		bank_mask = SC16IS7XX_IOCONTROL_3_0;
		low_pin = 0;
		high_pin = 3;
	} else {
		bank_mask = SC16IS7XX_IOCONTROL_7_4;
		low_pin = 4;
		high_pin = SC16IS7XX_NPINS - 1;
	}

	if (flags & GPIO_PIN_ALT0) {
		sc16is7xx_gpio_ctl_alt0(sc, bank_mask, low_pin, high_pin, flags);
	} else {
		sc16is7xx_gpio_ctl_inout(sc, bank_mask, low_pin, high_pin, pin, flags);
	}
}
/* sysctl */

int
sc16is7xx_verify_poll(SYSCTLFN_ARGS)
{
	int error, t;
	struct sysctlnode node;

	node = *rnode;
	t = *(int *)rnode->sysctl_data;
	node.sysctl_data = &t;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error || newp == NULL)
		return error;

	if (t < 1)
		return EINVAL;

	*(int *)rnode->sysctl_data = t;

	return 0;
}

int
sc16is7xx_verify_freq_sysctl(SYSCTLFN_ARGS)
{
	struct sc16is7xx_sc *sc;
	struct sc16is7xx_tty_softc *csc;
	int error, t;
	struct sysctlnode node;

	node = *rnode;
	sc = node.sysctl_data;
	t = sc->sc_frequency;
	node.sysctl_data = &t;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error || newp == NULL)
		return error;

	if (t < 1)
		return EINVAL;

	sc->sc_frequency = t;

	for (int i = 0; i <= 1; i++){
		if (sc->sc_ttydevchannel[i] != NULL) {
			csc = device_private(sc->sc_ttydevchannel[i]);
			if (csc != NULL)
				csc->sc_com.sc_frequency = sc->sc_frequency;
		}
	}

	return 0;
}

static int
sc16is7xx_sysctl_init(struct sc16is7xx_sc *sc)
{
	int error;
	const struct sysctlnode *cnode;
	int sysctlroot_num;

	if ((error = sysctl_createv(&sc->sc_sc16is7xx_log, 0, NULL, &cnode,
		    0, CTLTYPE_NODE, device_xname(sc->sc_dev),
		    SYSCTL_DESCR("sc16ix7xx controls"), NULL, 0, NULL, 0, CTL_HW,
		    CTL_CREATE, CTL_EOL)) != 0)
		return error;

	sysctlroot_num = cnode->sysctl_num;

	if ((error = sysctl_createv(&sc->sc_sc16is7xx_log, 0, NULL, &cnode,
		    CTLFLAG_READWRITE, CTLTYPE_INT, "frequency",
		    SYSCTL_DESCR("Frequency of the oscillator in Hz"), sc16is7xx_verify_freq_sysctl, 0,
		    (void *)sc, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
		    CTL_EOL)) != 0)
		return error;

	if ((error = sysctl_createv(&sc->sc_sc16is7xx_log, 0, NULL, &cnode,
		    CTLFLAG_READWRITE, CTLTYPE_INT, "poll",
		    SYSCTL_DESCR("In polling mode, how often to check the status register in ms"), sc16is7xx_verify_poll, 0,
		    &sc->sc_poll, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
		    CTL_EOL)) != 0)
		return error;

#define __DEBUGGING_THE_KTHREAD 1
#ifdef __DEBUGGING_THE_KTHREAD
	if ((error = sysctl_createv(&sc->sc_sc16is7xx_log, 0, NULL, &cnode,
		    CTLFLAG_READONLY, CTLTYPE_INT, "thread_state",
		    SYSCTL_DESCR("Kernel thread state"), NULL, 0,
		    (int *)&sc->sc_thread_state, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
		    CTL_EOL)) != 0)
		return error;

	if ((error = sysctl_createv(&sc->sc_sc16is7xx_log, 0, NULL, &cnode,
		    CTLFLAG_READONLY, CTLTYPE_INT, "de_count",
		    SYSCTL_DESCR("Disable / Enable count"), NULL, 0,
		    &sc->sc_de_count, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
		    CTL_EOL)) != 0)
		return error;
#endif

	return 0;
}
/* attach, detach and such */

static int
sc16is7xx_print(void *aux, const char *pnp)
{
	struct sc16is7xx_tty_attach_args *aa = aux;

	if (pnp)
		aprint_normal("%s", pnp);
	aprint_normal(" channel %d", aa->aa_channel);
	return (UNCONF);
}

void
sc16is7xx_attach(struct sc16is7xx_sc *sc)
{
	int error;
	char chip_type[SC16IS7XX_TYPE_STRINGLEN];
	int num_channels;
	int num_gpio;
	uint8_t buf[1];
	uint8_t iocontrol_reg;
	struct sc16is7xx_tty_attach_args aa;
	int reset_count = 0;

	aprint_normal("\n");

	sc->sc_frequency = SC16IS7XX_DEFAULT_FREQUENCY;
	sc->sc_poll = SC16IS7XX_DEFAULT_POLL;
	sc->sc_thread_state = SC16IS7XX_THREAD_GPIO;
	sc->sc_thread = NULL;
	sc->sc_wq = NULL;
	sc->sc_ih = NULL;
	sc->sc_sih = NULL;
	sc->sc_de_count = 0;

	mutex_init(&sc->sc_thread_mutex, MUTEX_DEFAULT, IPL_SOFTSERIAL);
	cv_init(&sc->sc_threadvar, "sc16is_cv");

	if ((error = sc16is7xx_sysctl_init(sc)) != 0) {
		aprint_error_dev(sc->sc_dev, "Can't setup sysctl tree (%d)\n", error);
		goto out;
	}

	/* Reset of the chip is a little odd.  Setting the SRESET bit is the
	 * only write that will NACK.  So, an expected error will result in
	 * that case.  Just ignore the error and read a few times to make sure
	 * that the reset is done.  After the reset, one must delay at least
	 * 3us before trying to talk to the chip again. */
	error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 0, &iocontrol_reg, 1);
	if (!error) {
		iocontrol_reg |= SC16IS7XX_IOCONTROL_SRESET;
		sc->sc_funcs->write_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 0, &iocontrol_reg, 1);
		delay(5);
		do {
			error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 0, &iocontrol_reg, 1);
			if (!error) {
				if (iocontrol_reg & SC16IS7XX_IOCONTROL_SRESET) {
					delay(2);
				}
				reset_count++;
			}
		} while ((iocontrol_reg & SC16IS7XX_IOCONTROL_SRESET) &&
		    (reset_count < 100) &&
		    (!error));
	}
	if (!error) {
		if (iocontrol_reg & SC16IS7XX_IOCONTROL_SRESET) {
			aprint_error_dev(sc->sc_dev,
			    "Chip did not reset in time.  reset_count=%d\n", reset_count);
		}
	} else {
		aprint_error_dev(sc->sc_dev,
		    "Error resetting chip: error=%d, reset_count=%d\n",
		    error, reset_count);
	}

	/* After a reset, the LCR register will be 0x1d.  If this isn't the
	 * case with channel 1, then channel 1 does not exist and this is a
	 * single UART chip, that is a SC16IS740, SC16IS750 or SC16IS760 and
	 * not a SC16IS752 or SC16IS762.
	 *
	 * There does not appear to be a way to distinguish a SC16IS740 /
	 * SC16IS741 from a SC16IS750 / SC16IS760.  The GPIO registers exist in
	 * both variants and appear to behave the same.  Obviously the physical
	 * pins are missing from the SC16IS74x branch of the family.  A bit
	 * more detail is available if you have a system with FDT.
	 *
	 * */

	num_channels = 1;
	num_gpio = SC16IS7XX_NPINS;
	strncpy(chip_type, "UNKNOWN", SC16IS7XX_TYPE_STRINGLEN);

	error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_LCR, 1, buf, 1);
	if (!error) {
		if (buf[0] == 0x1d) {
			strncpy(chip_type, "SC16IS752/SC16IS762", SC16IS7XX_TYPE_STRINGLEN);
			num_channels = 2;
		} else {
			strncpy(chip_type, "SC16IS740/SC16IS741/SC16IS750/SC16IS760", SC16IS7XX_TYPE_STRINGLEN);
		}
	}
	sc->sc_num_channels = num_channels;

	aprint_normal_dev(sc->sc_dev, "NXP %s\n", chip_type);

	sc->sc_ttydevchannel[0] = sc->sc_ttydevchannel[1] = NULL;

	bool use_polling = true;

#ifdef FDT

	error = workqueue_create(&sc->sc_wq, device_xname(sc->sc_dev),
	    sc16is7xx_wq, sc, PRI_SOFTSERIAL, IPL_SOFTSERIAL, WQ_MPSAFE);
	if (error) {
		aprint_error_dev(sc->sc_dev,
		    "Could not create workqueue: %d\n",
		    error);
	}
	if (!error &&
	    devhandle_type(device_handle(sc->sc_dev)) == DEVHANDLE_TYPE_OF) {
		char intrstr[128];

		sc->sc_phandle = devhandle_to_of(device_handle(sc->sc_dev));

		if (!of_hasprop(sc->sc_phandle, "gpio-controller")) {
			num_gpio = 0;

			/* If there is an indication that the GPIO is not
			 * desired, turn the pins into modem control pins. */
			error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 1, &iocontrol_reg, 1);
			if (error)
				goto out;
			if (num_channels == 2)
				iocontrol_reg |= SC16IS7XX_IOCONTROL_7_4 | SC16IS7XX_IOCONTROL_3_0;
			else
				iocontrol_reg |= SC16IS7XX_IOCONTROL_7_4;	/* No strictly correct
										 * for the SC16IS74x */
			error = sc->sc_funcs->write_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 1, &iocontrol_reg, 1);
			if (error)
				goto out;
		}
		if (fdtbus_intr_str(sc->sc_phandle, 0,
		    intrstr, sizeof(intrstr))) {

			aprint_normal_dev(sc->sc_dev, "interrupting on %s\n", intrstr);

			sc->sc_ih = fdtbus_intr_establish(sc->sc_phandle, 0, IPL_VM, 0,
			    sc16is7xx_intr, sc);

			if (sc->sc_ih == NULL) {
				aprint_error_dev(sc->sc_dev,
				    "unable to establish interrupt\n");
			} else {
				sc->sc_sih =
				    softint_establish(SOFTINT_SERIAL, sc16is7xx_softintr, sc);
				if (sc->sc_sih == NULL) {
					aprint_error_dev(sc->sc_dev,
					    "unable to establish soft interrupt\n");
					fdtbus_intr_disestablish(sc->sc_phandle, sc->sc_ih);
					sc->sc_ih = NULL;
				} else {
					use_polling = false;
				}
			}
		}
		const u_int * cf;
		int len;
		cf = fdtbus_get_prop(sc->sc_phandle, "clock-frequency", &len);
		if (cf != NULL && len > 0) {
			sc->sc_frequency = be32toh(cf[0]);
		}
	}

#endif

	if (use_polling) {
		aprint_normal_dev(sc->sc_dev, "polling for interrupts\n");
		sc->sc_thread_state = SC16IS7XX_THREAD_STOPPED;
	}

	for (int i = 0; i < num_channels; i++){
		aa.aa_channel = i;

		sc->sc_ttydevchannel[i] = config_found(sc->sc_dev, &aa, sc16is7xx_print,
		    CFARGS(.submatch = config_stdsubmatch,
		    .iattr = "sc16is7xxbus"));

	}

	if (num_gpio > 0) {
		struct gpiobus_attach_args gba;
		uint8_t iodir_reg;
		int c = 3;

		if (num_channels == 2)
			c = -1;

		error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IOCONTROL, 1, &iocontrol_reg, 1);
		if (error)
			goto out;
		error = sc->sc_funcs->read_reg(sc, SC16IS7XX_REGISTER_IODIR, 1, &iodir_reg, 1);
		if (error)
			goto out;

		for (int i = 0; i < num_gpio; i++){
			sc->sc_gpio_pins[i].pin_num = i;
			sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INPUT;
			sc->sc_gpio_pins[i].pin_caps |= GPIO_PIN_OUTPUT;
			if (i > c)
				sc->sc_gpio_pins[i].pin_caps |= GPIO_PIN_ALT0;
			sc->sc_gpio_pins[i].pin_flags =
			    sc16is7xx_to_gpio_flags(sc, i, num_channels, iocontrol_reg, iodir_reg);
			sc->sc_gpio_pins[i].pin_intrcaps = 0;
			snprintf(sc->sc_gpio_pins[i].pin_defname, 4, "GP%d", i);
		}

		sc->sc_gpio_gc.gp_cookie = sc;
		sc->sc_gpio_gc.gp_pin_read = sc16is7xx_gpio_pin_read;
		sc->sc_gpio_gc.gp_pin_write = sc16is7xx_gpio_pin_write;
		sc->sc_gpio_gc.gp_pin_ctl = sc16is7xx_gpio_pin_ctl;

		gba.gba_gc = &sc->sc_gpio_gc;
		gba.gba_pins = sc->sc_gpio_pins;
		gba.gba_npins = SC16IS7XX_NPINS;

		sc->sc_gpio_dev = config_found(sc->sc_dev, &gba, gpiobus_print,
		    CFARGS(.iattr = "gpiobus"));
	}

out:

	return;
}

int
sc16is7xx_detach(struct sc16is7xx_sc *sc, int flags)
{
	int err = 0;

	err = config_detach_children(sc->sc_dev, flags);

	mutex_enter(&sc->sc_thread_mutex);
	if (sc->sc_thread &&
	    sc->sc_thread_state != SC16IS7XX_THREAD_GPIO &&
	    sc->sc_thread_state != SC16IS7XX_THREAD_STOPPED &&
	    sc->sc_thread_state != SC16IS7XX_THREAD_EXIT) {
		sc->sc_thread_state = SC16IS7XX_THREAD_EXIT;
		cv_signal(&sc->sc_threadvar);
		mutex_exit(&sc->sc_thread_mutex);
		kthread_join(sc->sc_thread);
	} else {
		mutex_exit(&sc->sc_thread_mutex);
	}
#ifdef FDT
	if (sc->sc_ih != NULL)
		fdtbus_intr_disestablish(sc->sc_phandle, sc->sc_ih);

	if (sc->sc_sih != NULL)
		softint_disestablish(sc->sc_sih);
#endif

	sysctl_teardown(&sc->sc_sc16is7xx_log);

	cv_destroy(&sc->sc_threadvar);
	mutex_destroy(&sc->sc_thread_mutex);

	return err;
}
