// SPDX-License-Identifier: GPL-2.0

/***************************************************************************
 *   copyright		  : (C) 2001, 2002 by Frank Mori Hess
 ***************************************************************************/

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#define dev_fmt pr_fmt

#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <asm/dma.h>
#include <linux/io.h>
#include <linux/bitops.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/delay.h>

#include "gpibP.h"
#include "tms9914.h"

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("GPIB library for tms9914");

static unsigned int update_status_nolock(struct gpib_board *board, struct tms9914_priv *priv);

int tms9914_take_control(struct gpib_board *board, struct tms9914_priv *priv, int synchronous)
{
	int i;
	const int timeout = 100;

	if (synchronous)
		write_byte(priv, AUX_TCS, AUXCR);
	else
		write_byte(priv, AUX_TCA, AUXCR);
	// busy wait until ATN is asserted
	for (i = 0; i < timeout; i++) {
		if ((read_byte(priv, ADSR) & HR_ATN))
			break;
		udelay(1);
	}
	if (i == timeout)
		return -ETIMEDOUT;

	clear_bit(WRITE_READY_BN, &priv->state);

	return 0;
}
EXPORT_SYMBOL_GPL(tms9914_take_control);

/*
 * The agilent 82350B has a buggy implementation of tcs which interferes with the
 * operation of tca.  It appears to be based on the controller state machine
 * described in the TI 9900 TMS9914A data manual published in 1982.  This
 * manual describes tcs as putting the controller into a CWAS
 * state where it waits indefinitely for ANRS and ignores tca.	Since a
 * functioning tca is far more important than tcs, we work around the
 * problem by never issuing tcs.
 *
 * I don't know if this problem exists in the real tms9914a or just in the fpga
 * of the 82350B.  For now, only the agilent_82350b uses this workaround.
 * The rest of the tms9914 based drivers still use tms9914_take_control
 * directly (which does issue tcs).
 */
int tms9914_take_control_workaround(struct gpib_board *board,
				    struct tms9914_priv *priv, int synchronous)
{
	if (synchronous)
		return -ETIMEDOUT;
	return tms9914_take_control(board, priv, synchronous);
}
EXPORT_SYMBOL_GPL(tms9914_take_control_workaround);

int tms9914_go_to_standby(struct gpib_board *board, struct tms9914_priv *priv)
{
	int i;
	const int timeout = 1000;

	write_byte(priv, AUX_GTS, AUXCR);
	// busy wait until ATN is released
	for (i = 0; i < timeout; i++) {
		if ((read_byte(priv, ADSR) & HR_ATN) == 0)
			break;
		udelay(1);
	}
	if (i == timeout)
		return -ETIMEDOUT;

	clear_bit(COMMAND_READY_BN, &priv->state);

	return 0;
}
EXPORT_SYMBOL_GPL(tms9914_go_to_standby);

void tms9914_interface_clear(struct gpib_board *board, struct tms9914_priv *priv, int assert)
{
	if (assert) {
		write_byte(priv, AUX_SIC | AUX_CS, AUXCR);

		set_bit(CIC_NUM, &board->status);
	} else {
		write_byte(priv, AUX_SIC, AUXCR);
	}
}
EXPORT_SYMBOL_GPL(tms9914_interface_clear);

void tms9914_remote_enable(struct gpib_board *board, struct tms9914_priv *priv, int enable)
{
	if (enable)
		write_byte(priv, AUX_SRE | AUX_CS, AUXCR);
	else
		write_byte(priv, AUX_SRE, AUXCR);
}
EXPORT_SYMBOL_GPL(tms9914_remote_enable);

int tms9914_request_system_control(struct gpib_board *board, struct tms9914_priv *priv,
				   int request_control)
{
	if (request_control) {
		write_byte(priv, AUX_RQC, AUXCR);
	} else {
		clear_bit(CIC_NUM, &board->status);
		write_byte(priv, AUX_RLC, AUXCR);
	}
	return 0;
}
EXPORT_SYMBOL_GPL(tms9914_request_system_control);

unsigned int tms9914_t1_delay(struct gpib_board *board, struct tms9914_priv *priv,
			      unsigned int nano_sec)
{
	static const int clock_period = 200;	// assuming 5Mhz input clock
	int num_cycles;

	num_cycles = 12;

	if (nano_sec <= 8 * clock_period) {
		write_byte(priv, AUX_STDL | AUX_CS, AUXCR);
		num_cycles = 8;
	} else {
		write_byte(priv, AUX_STDL, AUXCR);
	}

	if (nano_sec <= 4 * clock_period) {
		write_byte(priv, AUX_VSTDL | AUX_CS, AUXCR);
		num_cycles = 4;
	} else {
		write_byte(priv, AUX_VSTDL, AUXCR);
	}

	return num_cycles * clock_period;
}
EXPORT_SYMBOL_GPL(tms9914_t1_delay);

void tms9914_return_to_local(const struct gpib_board *board, struct tms9914_priv *priv)
{
	write_byte(priv, AUX_RTL, AUXCR);
}
EXPORT_SYMBOL_GPL(tms9914_return_to_local);

void tms9914_set_holdoff_mode(struct tms9914_priv *priv, enum tms9914_holdoff_mode mode)
{
	switch (mode) {
	case TMS9914_HOLDOFF_NONE:
		write_byte(priv, AUX_HLDE, AUXCR);
		write_byte(priv, AUX_HLDA, AUXCR);
		break;
	case TMS9914_HOLDOFF_EOI:
		write_byte(priv, AUX_HLDE | AUX_CS, AUXCR);
		write_byte(priv, AUX_HLDA, AUXCR);
		break;
	case TMS9914_HOLDOFF_ALL:
		write_byte(priv, AUX_HLDE, AUXCR);
		write_byte(priv, AUX_HLDA | AUX_CS, AUXCR);
		break;
	default:
		pr_err("bug! bad holdoff mode %i\n", mode);
		break;
	}
	priv->holdoff_mode = mode;
}
EXPORT_SYMBOL_GPL(tms9914_set_holdoff_mode);

void tms9914_release_holdoff(struct tms9914_priv *priv)
{
	if (priv->holdoff_active) {
		write_byte(priv, AUX_RHDF, AUXCR);
		priv->holdoff_active = 0;
	}
}
EXPORT_SYMBOL_GPL(tms9914_release_holdoff);

int tms9914_enable_eos(struct gpib_board *board, struct tms9914_priv *priv, u8 eos_byte,
		       int compare_8_bits)
{
	priv->eos = eos_byte;
	priv->eos_flags = REOS;
	if (compare_8_bits)
		priv->eos_flags |= BIN;
	return 0;
}
EXPORT_SYMBOL(tms9914_enable_eos);

void tms9914_disable_eos(struct gpib_board *board, struct tms9914_priv *priv)
{
	priv->eos_flags &= ~REOS;
}
EXPORT_SYMBOL(tms9914_disable_eos);

int tms9914_parallel_poll(struct gpib_board *board, struct tms9914_priv *priv, u8 *result)
{
	// execute parallel poll
	write_byte(priv, AUX_CS | AUX_RPP, AUXCR);
	udelay(2);
	*result = read_byte(priv, CPTR);
	// clear parallel poll state
	write_byte(priv, AUX_RPP, AUXCR);
	return 0;
}
EXPORT_SYMBOL(tms9914_parallel_poll);

static void set_ppoll_reg(struct tms9914_priv *priv, int enable,
			  unsigned int dio_line, int sense, int ist)
{
	u8 dio_byte;

	if (enable && ((sense && ist) || (!sense && !ist))) {
		dio_byte = 1 << (dio_line - 1);
		write_byte(priv, dio_byte, PPR);
	} else {
		write_byte(priv, 0, PPR);
	}
}

void tms9914_parallel_poll_configure(struct gpib_board *board,
				     struct tms9914_priv *priv, u8 config)
{
	priv->ppoll_enable = (config & PPC_DISABLE) == 0;
	priv->ppoll_line = (config & PPC_DIO_MASK) + 1;
	priv->ppoll_sense = (config & PPC_SENSE) != 0;
	set_ppoll_reg(priv, priv->ppoll_enable, priv->ppoll_line, priv->ppoll_sense, board->ist);
}
EXPORT_SYMBOL(tms9914_parallel_poll_configure);

void tms9914_parallel_poll_response(struct gpib_board *board,
				    struct tms9914_priv *priv, int ist)
{
	set_ppoll_reg(priv, priv->ppoll_enable, priv->ppoll_line, priv->ppoll_sense, ist);
}
EXPORT_SYMBOL(tms9914_parallel_poll_response);

void tms9914_serial_poll_response(struct gpib_board *board,
				  struct tms9914_priv *priv, u8 status)
{
	unsigned long flags;

	spin_lock_irqsave(&board->spinlock, flags);
	write_byte(priv, status, SPMR);
	priv->spoll_status = status;
	if (status & request_service_bit)
		write_byte(priv, AUX_RSV2 | AUX_CS, AUXCR);
	else
		write_byte(priv, AUX_RSV2, AUXCR);
	spin_unlock_irqrestore(&board->spinlock, flags);
}
EXPORT_SYMBOL(tms9914_serial_poll_response);

u8 tms9914_serial_poll_status(struct gpib_board *board, struct tms9914_priv *priv)
{
	u8 status;
	unsigned long flags;

	spin_lock_irqsave(&board->spinlock, flags);
	status = priv->spoll_status;
	spin_unlock_irqrestore(&board->spinlock, flags);

	return status;
}
EXPORT_SYMBOL(tms9914_serial_poll_status);

int tms9914_primary_address(struct gpib_board *board,
			    struct tms9914_priv *priv, unsigned int address)
{
	// put primary address in address0
	write_byte(priv, address & ADDRESS_MASK, ADR);
	return 0;
}
EXPORT_SYMBOL(tms9914_primary_address);

int tms9914_secondary_address(struct gpib_board *board, struct tms9914_priv *priv,
			      unsigned int address, int enable)
{
	if (enable)
		priv->imr1_bits |= HR_APTIE;
	else
		priv->imr1_bits &= ~HR_APTIE;

	write_byte(priv, priv->imr1_bits, IMR1);
	return 0;
}
EXPORT_SYMBOL(tms9914_secondary_address);

unsigned int tms9914_update_status(struct gpib_board *board, struct tms9914_priv *priv,
				   unsigned int clear_mask)
{
	unsigned long flags;
	unsigned int retval;

	spin_lock_irqsave(&board->spinlock, flags);
	retval = update_status_nolock(board, priv);
	board->status &= ~clear_mask;
	spin_unlock_irqrestore(&board->spinlock, flags);

	return retval;
}
EXPORT_SYMBOL(tms9914_update_status);

static void update_talker_state(struct tms9914_priv *priv, unsigned int address_status_bits)
{
	if (address_status_bits & HR_TA)	{
		if (address_status_bits & HR_ATN)
			priv->talker_state = talker_addressed;
		else
			/*
			 * this could also be serial_poll_active, but the tms9914 provides no
			 * way to distinguish, so we'll assume talker_active
			 */
			priv->talker_state = talker_active;
	} else {
		priv->talker_state = talker_idle;
	}
}

static void update_listener_state(struct tms9914_priv *priv, unsigned int address_status_bits)
{
	if (address_status_bits & HR_LA)	{
		if (address_status_bits & HR_ATN)
			priv->listener_state = listener_addressed;
		else
			priv->listener_state = listener_active;
	} else {
		priv->listener_state = listener_idle;
	}
}

static unsigned int update_status_nolock(struct gpib_board *board, struct tms9914_priv *priv)
{
	int address_status;
	int bsr_bits;

	address_status = read_byte(priv, ADSR);

	// check for remote/local
	if (address_status & HR_REM)
		set_bit(REM_NUM, &board->status);
	else
		clear_bit(REM_NUM, &board->status);
	// check for lockout
	if (address_status & HR_LLO)
		set_bit(LOK_NUM, &board->status);
	else
		clear_bit(LOK_NUM, &board->status);
	// check for ATN
	if (address_status & HR_ATN)
		set_bit(ATN_NUM, &board->status);
	else
		clear_bit(ATN_NUM, &board->status);
	// check for talker/listener addressed
	update_talker_state(priv, address_status);
	if (priv->talker_state == talker_active || priv->talker_state == talker_addressed)
		set_bit(TACS_NUM, &board->status);
	else
		clear_bit(TACS_NUM, &board->status);

	update_listener_state(priv, address_status);
	if (priv->listener_state == listener_active || priv->listener_state == listener_addressed)
		set_bit(LACS_NUM, &board->status);
	else
		clear_bit(LACS_NUM, &board->status);
	// Check for SRQI - not reset elsewhere except in autospoll
	if (board->status & SRQI) {
		bsr_bits = read_byte(priv, BSR);
		if (!(bsr_bits & BSR_SRQ_BIT))
			clear_bit(SRQI_NUM, &board->status);
	}

	dev_dbg(board->gpib_dev, "status 0x%lx, state 0x%lx\n", board->status, priv->state);

	return board->status;
}

int tms9914_line_status(const struct gpib_board *board, struct tms9914_priv *priv)
{
	int bsr_bits;
	int status = VALID_ALL;

	bsr_bits = read_byte(priv, BSR);

	if (bsr_bits & BSR_REN_BIT)
		status |= BUS_REN;
	if (bsr_bits & BSR_IFC_BIT)
		status |= BUS_IFC;
	if (bsr_bits & BSR_SRQ_BIT)
		status |= BUS_SRQ;
	if (bsr_bits & BSR_EOI_BIT)
		status |= BUS_EOI;
	if (bsr_bits & BSR_NRFD_BIT)
		status |= BUS_NRFD;
	if (bsr_bits & BSR_NDAC_BIT)
		status |= BUS_NDAC;
	if (bsr_bits & BSR_DAV_BIT)
		status |= BUS_DAV;
	if (bsr_bits & BSR_ATN_BIT)
		status |= BUS_ATN;

	return status;
}
EXPORT_SYMBOL(tms9914_line_status);

static int check_for_eos(struct tms9914_priv *priv, u8 byte)
{
	static const u8 seven_bit_compare_mask = 0x7f;

	if ((priv->eos_flags & REOS) == 0)
		return 0;

	if (priv->eos_flags & BIN) {
		if (priv->eos == byte)
			return 1;
	} else	{
		if ((priv->eos & seven_bit_compare_mask) == (byte & seven_bit_compare_mask))
			return 1;
	}
	return 0;
}

static int wait_for_read_byte(struct gpib_board *board, struct tms9914_priv *priv)
{
	if (wait_event_interruptible(board->wait,
				     test_bit(READ_READY_BN, &priv->state) ||
				     test_bit(DEV_CLEAR_BN, &priv->state) ||
				     test_bit(TIMO_NUM, &board->status)))
		return -ERESTARTSYS;

	if (test_bit(TIMO_NUM, &board->status))
		return -ETIMEDOUT;

	if (test_bit(DEV_CLEAR_BN, &priv->state))
		return -EINTR;
	return 0;
}

static inline u8 tms9914_read_data_in(struct gpib_board *board,
				      struct tms9914_priv *priv, int *end)
{
	unsigned long flags;
	u8 data;

	spin_lock_irqsave(&board->spinlock, flags);
	clear_bit(READ_READY_BN, &priv->state);
	data = read_byte(priv, DIR);
	if (test_and_clear_bit(RECEIVED_END_BN, &priv->state))
		*end = 1;
	else
		*end = 0;
	switch (priv->holdoff_mode) {
	case TMS9914_HOLDOFF_EOI:
		if (*end)
			priv->holdoff_active = 1;
		break;
	case TMS9914_HOLDOFF_ALL:
		priv->holdoff_active = 1;
		break;
	case TMS9914_HOLDOFF_NONE:
		break;
	default:
		dev_err(board->gpib_dev, "bug! bad holdoff mode %i\n", priv->holdoff_mode);
		break;
	}
	spin_unlock_irqrestore(&board->spinlock, flags);

	return data;
}

static int pio_read(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer,
		    size_t length, int *end, size_t *bytes_read)
{
	ssize_t retval = 0;

	*bytes_read = 0;
	*end = 0;
	while (*bytes_read < length && *end == 0) {
		tms9914_release_holdoff(priv);
		retval = wait_for_read_byte(board, priv);
		if (retval < 0)
			return retval;
		buffer[(*bytes_read)++] = tms9914_read_data_in(board, priv, end);

		if (check_for_eos(priv, buffer[*bytes_read - 1]))
			*end = 1;
	}

	return retval;
}

int tms9914_read(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer,
		 size_t length, int *end, size_t *bytes_read)
{
	ssize_t retval = 0;
	size_t num_bytes;

	*end = 0;
	*bytes_read = 0;
	if (length == 0)
		return 0;

	clear_bit(DEV_CLEAR_BN, &priv->state);

	// transfer data (except for last byte)
	if (length > 1)	{
		if (priv->eos_flags & REOS)
			tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL);
		else
			tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_EOI);
		// PIO transfer
		retval = pio_read(board, priv, buffer, length - 1, end, &num_bytes);
		*bytes_read += num_bytes;
		if (retval < 0)
			return retval;
		buffer += num_bytes;
		length -= num_bytes;
	}
	// read last bytes if we haven't received an END yet
	if (*end == 0) {
		// make sure we holdoff after last byte read
		tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL);
		retval = pio_read(board, priv, buffer, length, end, &num_bytes);
		*bytes_read += num_bytes;
		if (retval < 0)
			return retval;
	}
	return 0;
}
EXPORT_SYMBOL(tms9914_read);

static int pio_write_wait(struct gpib_board *board, struct tms9914_priv *priv)
{
	// wait until next byte is ready to be sent
	if (wait_event_interruptible(board->wait,
				     test_bit(WRITE_READY_BN, &priv->state) ||
				     test_bit(BUS_ERROR_BN, &priv->state) ||
				     test_bit(DEV_CLEAR_BN, &priv->state) ||
				     test_bit(TIMO_NUM, &board->status)))
		return -ERESTARTSYS;

	if (test_bit(TIMO_NUM, &board->status))
		return -ETIMEDOUT;
	if (test_bit(BUS_ERROR_BN, &priv->state))
		return -EIO;
	if (test_bit(DEV_CLEAR_BN, &priv->state))
		return -EINTR;

	return 0;
}

static int pio_write(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer,
		     size_t length, size_t *bytes_written)
{
	ssize_t retval = 0;
	unsigned long flags;

	*bytes_written = 0;
	while (*bytes_written < length) {
		retval = pio_write_wait(board, priv);
		if (retval < 0)
			break;

		spin_lock_irqsave(&board->spinlock, flags);
		clear_bit(WRITE_READY_BN, &priv->state);
		write_byte(priv, buffer[(*bytes_written)++], CDOR);
		spin_unlock_irqrestore(&board->spinlock, flags);
	}
	retval = pio_write_wait(board, priv);
	if (retval < 0)
		return retval;

	return length;
}

int tms9914_write(struct gpib_board *board, struct tms9914_priv *priv,
		  u8 *buffer, size_t length, int send_eoi, size_t *bytes_written)
{
	ssize_t retval = 0;

	*bytes_written = 0;
	if (length == 0)
		return 0;

	clear_bit(BUS_ERROR_BN, &priv->state);
	clear_bit(DEV_CLEAR_BN, &priv->state);

	if (send_eoi)
		length-- ; /* save the last byte for sending EOI */

	if (length > 0)	{
		size_t num_bytes;
		// PIO transfer
		retval = pio_write(board, priv, buffer, length, &num_bytes);
		*bytes_written += num_bytes;
		if (retval < 0)
			return retval;
	}
	if (send_eoi) {
		size_t num_bytes;
		/*send EOI */
		write_byte(priv, AUX_SEOI, AUXCR);

		retval = pio_write(board, priv, &buffer[*bytes_written], 1, &num_bytes);
		*bytes_written += num_bytes;
	}
	return retval;
}
EXPORT_SYMBOL(tms9914_write);

static void check_my_address_state(struct gpib_board *board,
				   struct tms9914_priv *priv, int cmd_byte)
{
	if (cmd_byte == MLA(board->pad)) {
		priv->primary_listen_addressed = 1;
		// become active listener
		if (board->sad < 0)
			write_byte(priv, AUX_LON | AUX_CS, AUXCR);
	} else if (board->sad >= 0 && priv->primary_listen_addressed &&
		  cmd_byte == MSA(board->sad)) {
		// become active listener
		write_byte(priv, AUX_LON | AUX_CS, AUXCR);
	} else if (cmd_byte != MLA(board->pad) && (cmd_byte & 0xe0) == LAD) {
		priv->primary_listen_addressed = 0;
	} else if (cmd_byte == UNL) {
		priv->primary_listen_addressed = 0;
		write_byte(priv, AUX_LON, AUXCR);
	} else if (cmd_byte == MTA(board->pad))	{
		priv->primary_talk_addressed = 1;
		if (board->sad < 0)
			// make active talker
			write_byte(priv, AUX_TON | AUX_CS, AUXCR);
	} else if (board->sad >= 0 && priv->primary_talk_addressed &&
		   cmd_byte == MSA(board->sad)) {
		// become active talker
		write_byte(priv, AUX_TON | AUX_CS, AUXCR);
	} else if (cmd_byte != MTA(board->pad) && (cmd_byte & 0xe0) == TAD) {
		// Other Talk Address
		priv->primary_talk_addressed = 0;
		write_byte(priv, AUX_TON, AUXCR);
	} else if (cmd_byte == UNT) {
		priv->primary_talk_addressed = 0;
		write_byte(priv, AUX_TON, AUXCR);
	}
}

int tms9914_command(struct gpib_board *board, struct tms9914_priv *priv,  u8 *buffer,
		    size_t length, size_t *bytes_written)
{
	int retval = 0;
	unsigned long flags;

	*bytes_written = 0;
	while (*bytes_written < length) {
		if (wait_event_interruptible(board->wait,
					     test_bit(COMMAND_READY_BN,
						      &priv->state) ||
					     test_bit(TIMO_NUM, &board->status)))
			break;
		if (test_bit(TIMO_NUM, &board->status))
			break;

		spin_lock_irqsave(&board->spinlock, flags);
		clear_bit(COMMAND_READY_BN, &priv->state);
		write_byte(priv, buffer[*bytes_written], CDOR);
		spin_unlock_irqrestore(&board->spinlock, flags);

		check_my_address_state(board, priv, buffer[*bytes_written]);

		++(*bytes_written);
	}
	// wait until last command byte is written
	if (wait_event_interruptible(board->wait,
				     test_bit(COMMAND_READY_BN,
					      &priv->state) || test_bit(TIMO_NUM, &board->status)))
		retval = -ERESTARTSYS;
	if (test_bit(TIMO_NUM, &board->status))
		retval = -ETIMEDOUT;

	return retval;
}
EXPORT_SYMBOL(tms9914_command);

irqreturn_t tms9914_interrupt(struct gpib_board *board, struct tms9914_priv *priv)
{
	int status0, status1;

	// read interrupt status (also clears status)
	status0 = read_byte(priv, ISR0);
	status1 = read_byte(priv, ISR1);
	return tms9914_interrupt_have_status(board, priv, status0, status1);
}
EXPORT_SYMBOL(tms9914_interrupt);

irqreturn_t tms9914_interrupt_have_status(struct gpib_board *board, struct tms9914_priv *priv,
					  int status0, int status1)
{
	// record reception of END
	if (status0 & HR_END)
		set_bit(RECEIVED_END_BN, &priv->state);
	// get incoming data in PIO mode
	if ((status0 & HR_BI))
		set_bit(READ_READY_BN, &priv->state);
	if ((status0 & HR_BO))	{
		if (read_byte(priv, ADSR) & HR_ATN)
			set_bit(COMMAND_READY_BN, &priv->state);
		else
			set_bit(WRITE_READY_BN, &priv->state);
	}

	if (status0 & HR_SPAS) {
		priv->spoll_status &= ~request_service_bit;
		write_byte(priv, priv->spoll_status, SPMR);
		// FIXME: set SPOLL status bit
	}
	// record service request in status
	if (status1 & HR_SRQ)
		set_bit(SRQI_NUM, &board->status);
	// have been addressed (with secondary addressing disabled)
	if (status1 & HR_MA)
		// clear dac holdoff
		write_byte(priv, AUX_VAL, AUXCR);
	// unrecognized command received
	if (status1 & HR_UNC) {
		unsigned short command_byte = read_byte(priv, CPTR) & gpib_command_mask;

		switch (command_byte) {
		case PP_CONFIG:
			priv->ppoll_configure_state = 1;
			/*
			 * AUX_PTS generates another UNC interrupt on the next command byte
			 * if it is in the secondary address group (such as PPE and PPD).
			 */
			write_byte(priv, AUX_PTS, AUXCR);
			write_byte(priv, AUX_VAL, AUXCR);
			break;
		case PPU:
			tms9914_parallel_poll_configure(board, priv, command_byte);
			write_byte(priv, AUX_VAL, AUXCR);
			break;
		default:
			if (is_PPE(command_byte) || is_PPD(command_byte)) {
				if (priv->ppoll_configure_state) {
					tms9914_parallel_poll_configure(board, priv, command_byte);
					write_byte(priv, AUX_VAL, AUXCR);
				} else	{// bad parallel poll configure byte
					// clear dac holdoff
					write_byte(priv, AUX_INVAL, AUXCR);
				}
			} else	{
				// clear dac holdoff
				write_byte(priv, AUX_INVAL, AUXCR);
			}
			break;
		}

		if (in_primary_command_group(command_byte) && command_byte != PP_CONFIG)
			priv->ppoll_configure_state = 0;
	}

	if (status1 & HR_ERR) {
		dev_dbg(board->gpib_dev, "gpib bus error\n");
		set_bit(BUS_ERROR_BN, &priv->state);
	}

	if (status1 & HR_IFC) {
		push_gpib_event(board, EVENT_IFC);
		clear_bit(CIC_NUM, &board->status);
	}

	if (status1 & HR_GET) {
		push_gpib_event(board, EVENT_DEV_TRG);
		// clear dac holdoff
		write_byte(priv, AUX_VAL, AUXCR);
	}

	if (status1 & HR_DCAS) {
		push_gpib_event(board, EVENT_DEV_CLR);
		// clear dac holdoff
		write_byte(priv, AUX_VAL, AUXCR);
		set_bit(DEV_CLEAR_BN, &priv->state);
	}

	// check for being addressed with secondary addressing
	if (status1 & HR_APT) {
		if (board->sad < 0)
			dev_err(board->gpib_dev, "bug, APT interrupt without secondary addressing?\n");
		if ((read_byte(priv, CPTR) & gpib_command_mask) == MSA(board->sad))
			write_byte(priv, AUX_VAL, AUXCR);
		else
			write_byte(priv, AUX_INVAL, AUXCR);
	}

	if ((status0 & priv->imr0_bits) || (status1 & priv->imr1_bits))	{
		dev_dbg(board->gpib_dev, "isr0 0x%x, imr0 0x%x, isr1 0x%x, imr1 0x%x\n",
			status0, priv->imr0_bits, status1, priv->imr1_bits);
		update_status_nolock(board, priv);
		wake_up_interruptible(&board->wait);
	}
	return IRQ_HANDLED;
}
EXPORT_SYMBOL(tms9914_interrupt_have_status);

void tms9914_board_reset(struct tms9914_priv *priv)
{
	/* chip reset */
	write_byte(priv, AUX_CHIP_RESET | AUX_CS, AUXCR);

	/* disable all interrupts */
	priv->imr0_bits = 0;
	write_byte(priv, priv->imr0_bits, IMR0);
	priv->imr1_bits = 0;
	write_byte(priv, priv->imr1_bits, IMR1);
	write_byte(priv, AUX_DAI | AUX_CS, AUXCR);

	/* clear registers by reading */
	read_byte(priv, CPTR);
	read_byte(priv, ISR0);
	read_byte(priv, ISR1);

	write_byte(priv, 0, SPMR);

	/* parallel poll unconfigure */
	write_byte(priv, 0, PPR);
	/* request for data holdoff */
	tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL);
}
EXPORT_SYMBOL_GPL(tms9914_board_reset);

void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv)
{
	/* set GPIB address */
	tms9914_primary_address(board, priv, board->pad);
	tms9914_secondary_address(board, priv, board->sad, board->sad >= 0);

	/* enable tms9914 interrupts */
	priv->imr0_bits |= HR_MACIE | HR_RLCIE | HR_ENDIE | HR_BOIE | HR_BIIE |
		HR_SPASIE;
	priv->imr1_bits |= HR_MAIE | HR_SRQIE | HR_UNCIE | HR_ERRIE | HR_IFCIE |
		HR_GETIE | HR_DCASIE;
	write_byte(priv, priv->imr0_bits, IMR0);
	write_byte(priv, priv->imr1_bits, IMR1);
	write_byte(priv, AUX_DAI, AUXCR);

	/* turn off reset state */
	write_byte(priv, AUX_CHIP_RESET, AUXCR);
}
EXPORT_SYMBOL_GPL(tms9914_online);

#ifdef CONFIG_HAS_IOPORT
// wrapper for inb
u8 tms9914_ioport_read_byte(struct tms9914_priv *priv, unsigned int register_num)
{
	return inb(priv->iobase + register_num * priv->offset);
}
EXPORT_SYMBOL_GPL(tms9914_ioport_read_byte);

// wrapper for outb
void tms9914_ioport_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num)
{
	outb(data, priv->iobase + register_num * priv->offset);
	if (register_num == AUXCR)
		udelay(1);
}
EXPORT_SYMBOL_GPL(tms9914_ioport_write_byte);
#endif

// wrapper for readb
u8 tms9914_iomem_read_byte(struct tms9914_priv *priv, unsigned int register_num)
{
	return readb(priv->mmiobase + register_num * priv->offset);
}
EXPORT_SYMBOL_GPL(tms9914_iomem_read_byte);

// wrapper for writeb
void tms9914_iomem_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num)
{
	writeb(data, priv->mmiobase + register_num * priv->offset);
	if (register_num == AUXCR)
		udelay(1);
}
EXPORT_SYMBOL_GPL(tms9914_iomem_write_byte);

static int __init tms9914_init_module(void)
{
	return 0;
}

static void __exit tms9914_exit_module(void)
{
}

module_init(tms9914_init_module);
module_exit(tms9914_exit_module);

