⛏️ index : haiku.git

/*
* Copyright 2003-2006, Haiku.
* Distributed under the terms of the MIT License.
*
* A module driver for the generic mpu401 midi interface.
*
* Author:
*		Greg Crain (gsc70@comcast.net)
*
*  mpu401.c
*/


#include <ISA.h>
#include <KernelExport.h>
#include <OS.h>
#include <midi_driver.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "debug.h"
#include "mpu401_priv.h"


/* ----------
	midi_create_device -
----- */
/*-----------------------------*/
/*  Version 1 of mpu401 module */
/*-----------------------------*/

static status_t
create_device(int port, void ** out_storage, uint32 workarounds,
	void (*interrupt_op)(int32 op, void * card), void * card)
{
	mpu401device mpu_device;
	mpu401device *mpuptr;

	/* fill the structure with specific info from caller */
	mpu_device.addrport = port;
	mpu_device.workarounds = workarounds;
	mpu_device.V2 = FALSE;
	mpu_device.count = 1;
	mpu_device.interrupt_op = interrupt_op;
	mpu_device.card = card;

	LOG(("create_device count= %ld, addrport 0x%x, workarounds: %d",
		mpu_device.count, mpu_device.addrport, mpu_device.workarounds));

	// basically, each call to create device allocates memory for
	//a structure with the specific device info. The pointer to this is
	// returned back to calling driver
	mpuptr = (mpu401device*)malloc (sizeof(mpu401device));
	memcpy(mpuptr, &mpu_device, sizeof(mpu_device));
	*out_storage = (void *)mpuptr;

	return B_OK;
}


/*-----------------------------*/
/*  Version 2 of mpu401 module */
/*-----------------------------*/

static status_t
create_device_v2(int port, void ** out_storage, uint32 workarounds,
	void (*interrupt_op)(int32 op, void * card), void * card)
{
	mpu401device *mpuptr;
	mpu401device mpu_device;

	// not sure exactly how v2 of the module works.  I think that two ports
	// are created.  One for midi in data, and another for midi out data.
	// Instead of transfering data using a buffer and pointer, the midi
	// data is transfered via the global ports.
	// If the ports are created in the midi server, then the port id's
	// should be known in this hook call.
	// If the ports are created in this hook call, the the port id's
	// should be returned to the midi server.

	// One version of read/write hook functions are used for both v1, v2.
	//  Therefore, in those calls, it needs to be known whether the mididata
	// is to be read/written to a buffer, or to the port.

 	mpu_device.addrport = port;
	mpu_device.workarounds = workarounds;
	mpu_device.V2 = TRUE;
	mpu_device.count = 1;
	mpu_device.card = card;

	LOG(("create_device count= %ld, addrport 0x%x, workarounds: %d",
		mpu_device.count, mpu_device.addrport, mpu_device.workarounds));

	mpuptr = (mpu401device*)malloc(sizeof(mpu401device));
	memcpy(mpuptr, &mpu_device, sizeof(mpu_device));
	*out_storage = (void *)mpuptr;

	return B_OK;
}


/* ----------
	midi_delete_device
----- */
static status_t
delete_device(void * storage)
{
	mpu401device * mpu_device = (mpu401device *)storage;

	LOG(("device->addrport= 0x%x count= %ld\n",
		mpu_device->addrport, mpu_device->count));
	LOG(("delete_device: *storage:%p\n", storage));

	free(mpu_device);   // free the memory allocated in create_device

	return B_OK;
}


/* ----------
	midi_open - handle open() calls
----- */

static status_t
midi_open(void * storage, uint32 flags, void ** out_cookie)
{
	char semname[25];
	int ack_byte;
	mpu401device * mpu_device = (mpu401device *)storage;

	LOG(("open() flags: %ld, *storage: %p, **out_cookie: %p\n", flags,
		storage, out_cookie));
	LOG(("open: device->addrport 0x%x ,workarounds 0x%x\n",
		mpu_device->addrport, mpu_device->workarounds));

	// the undocumented V2 module is not complete
	// we will allow the device to be created since some drivers depend on it
	// but will return an error if the actual midi device is opened:
	if (mpu_device->V2 == TRUE)
		return B_ERROR;

	switch (mpu_device->workarounds) {
		case 0x11020004: // Still required for Creative Audigy, Audigy2
		case 0x11020005:
		case 0:
			// don't know the current mpu state
			PRINT(("reset MPU401\n"));
			Write_MPU401(mpu_device->addrport, UARTCMD,
				mpu_device->workarounds, MPU401_RESET);
			snooze(30000);
			Write_MPU401(mpu_device->addrport, UARTCMD,
				mpu_device->workarounds, MPU401_RESET);
			snooze(30000);
			ack_byte = Read_MPU401(mpu_device->addrport, UARTDATA,
				mpu_device->workarounds);
			PRINT(("enable UART mode\n"));
			Write_MPU401(mpu_device->addrport, UARTCMD,
				mpu_device->workarounds, MPU401_UART);
			snooze(30000);
			ack_byte = Read_MPU401(mpu_device->addrport, UARTDATA,
				mpu_device->workarounds);
			PRINT(("port cmd ack is 0x%x\n", ack_byte));
			*out_cookie = mpu_device;
			break;
		case 0x14121712:
			PRINT(("reset MPU401\n"));
			Write_MPU401(mpu_device->addrport, UARTDATA,
				mpu_device->workarounds, 0x00);
			snooze(30000);
			Write_MPU401(mpu_device->addrport, UARTCMD,
				mpu_device->workarounds, MPU401_RESET);
			snooze(30000);
			ack_byte = Read_MPU401(mpu_device->addrport, UARTDATA,
				mpu_device->workarounds);
			PRINT(("enable UART mode\n"));
			Write_MPU401(mpu_device->addrport, UARTDATA,
				mpu_device->workarounds, 0x00);
			snooze(30000);
			Write_MPU401(mpu_device->addrport, UARTCMD,
				mpu_device->workarounds, MPU401_UART);
			snooze(30000);
			ack_byte = Read_MPU401(mpu_device->addrport, UARTDATA,
				mpu_device->workarounds);
			PRINT(("port cmd ack is 0x%x\n", ack_byte));
			break;
		case 1:
			//  Some devices are always in UART mode
			PRINT(("already in UART mode\n"));
  			break;
		default:
			PRINT(("Unknown workaround: %d\n", mpu_device->workarounds));
			break;
	}  //end switch

	// Create Read semaphore for midi-in data
	sprintf(semname, "mpu401:%04x:read_sem", mpu_device->addrport);
	mpu_device->readsemaphore = create_sem(0, semname);

	// Create Write semaphore for midi-out data
	sprintf(semname,"mpu401:%04x:write_sem", mpu_device->addrport);
	mpu_device->writesemaphore = create_sem(1, semname);

	// clear midi-in buffer
	mbuf_bytes = 0;
	mbuf_current = 0;
	mbuf_start = 0;

  	//Enable midi interrupts
	mpu_device->interrupt_op(B_MPU_401_ENABLE_CARD_INT, mpu_device->card);

	if ((mpu_device->readsemaphore > B_OK)
		&& (mpu_device->writesemaphore > B_OK)) {
		atomic_add(&mpu_device->count, 1);
		PRINT(("midi_open() done (count = %x)\n", open_count));
		return B_OK;
	}
	return B_ERROR;
}


/* ----------
	midi_close - handle close() calls
----- */
static status_t
midi_close(void * cookie)
{
	mpu401device * mpu_device = (mpu401device *)cookie;

	if (mpu_device->count <= 0)
 	   return B_ERROR;

	//Disable soundcard midi interrupts
	mpu_device->interrupt_op(B_MPU_401_DISABLE_CARD_INT, mpu_device->card);

	// Delete the semaphores
	delete_sem(mpu_device->readsemaphore);
	delete_sem(mpu_device->writesemaphore);

	atomic_add(&mpu_device->count, -1);
  	PRINT(("midi_close() done (count = %" B_PRId32 ")\n", mpu_device->count));

	return B_OK;
}


/* ----------
	midi_free - free up allocated memory
----- */
static status_t
midi_free(void * cookie)
{
	LOG(("midi_free()\n"));
	return B_OK;
}


/* ----------
	midi_control - handle control() calls
----- */
static status_t
midi_control(void * cookie, uint32 op, void * data, size_t len)
{
	//mpu401device *mpu_device = (mpu401device *)cookie;

	/* I don't think this is ever called ...*/
	LOG(("midi_control()\n"));
	return B_OK;
}


/* ----------
	midi_read - handle read() calls
----- */
static status_t
midi_read(void *cookie, off_t pos, void *buffer, size_t *num_bytes)
{
	/* The actual midi data is read from the device in the interrupt handler;
	   this reads and returns the data from a buffer */

	unsigned char *data;
	unsigned int i;
	cpu_status status __attribute__((unused));
	status_t bestat;
	mpu401device *mpu_device = (mpu401device *)cookie;

 	data = (unsigned char*)buffer;

	i = 0;
	*num_bytes = 0;
	bestat = acquire_sem_etc(mpu_device->readsemaphore, 1,
		B_CAN_INTERRUPT, 0);
	if (bestat == B_INTERRUPTED) {
		//PRINT(("acquire_sem B_INTERRUPTED!\n"));
		return B_INTERRUPTED;
	}
	if (bestat != B_OK) {
		TRACE(("acquire_sem not B_OK %d\n",(int)bestat));
		*num_bytes = 1;
		return B_INTERRUPTED;
	} else {
#ifdef __HAIKU__
		if (user_memcpy(data+i, &(mpubuffer[mbuf_start]),
			sizeof(unsigned char)) == B_OK) {
#else
		status = lock();
		*(data+i) = mpubuffer[mbuf_start];
#endif
		i++;
		mbuf_start++; // pointer to data in ringbuffer
		if (mbuf_start >= (MBUF_ELEMENTS-1))
			mbuf_start = 0; //wraparound of ringbuffer
		*num_bytes = 1; // How many bytes are being returned in buffer
		if (mbuf_bytes > 0)
			mbuf_bytes--; // bytes read from buffer, so decrement buffer count
#ifdef __HAIKU__
		}
#else
   		unlock(status);
#endif
   		//PRINT(("bytes in buffer: %d\n",mbuf_bytes));
	}

	return B_OK;
}


/* ----------
	midi_write - handle write() calls
----- */
static status_t
midi_write(void * cookie, off_t pos, const void * data, size_t * num_bytes)
{
	unsigned char *bufdata;
	uint32 i;
	size_t count;

	mpu401device *mpu_device = (mpu401device *)cookie;
	bufdata = (unsigned char*)data;	/* Pointer to midi data buffer */
	count = *num_bytes;

	/* Only for deep debugging..will slow things down */
	/*PRINT(("write %d bytes, addrport 0x%x, workarounds 0x%x\n",
		(int)count, mpu_device->addrport, mpu_device->workarounds));*/

	acquire_sem(mpu_device->writesemaphore);
	for (i = 0; i < count; i++) {
		// wait until device is ready
		while ((Read_MPU401(mpu_device->addrport, UARTCMD,
					mpu_device->workarounds) & MPU401_OK2WR));

		Write_MPU401(mpu_device->addrport, UARTDATA,
			mpu_device->workarounds, *(bufdata+i));
	}

	*num_bytes = 0;
	release_sem(mpu_device->writesemaphore);

	return B_OK;
}


/* ----------
	interrupt_hook - handle interrupts for mpu401 data
----- */
static bool
interrupt_hook(void * cookie)
{
	mpu401device *mpu_device = (mpu401device *)cookie;

	/* Only for deep debugging..will slow things down */
	//PRINT(("irq! port: 0x%x\n",mpu_device->addrport));

	/* Input data is available when bit 7 of the Status port is zero.
	Conversely, when bit 7 is is a one, no MIDI data is available.
	Reading from the data port will often clear the interrupt signal
	depending on the sound card. */

	if ((Read_MPU401(mpu_device->addrport, UARTCMD,
			mpu_device->workarounds) & MPU401_OK2RD) == 0) {
		/* Okay, midi data waiting to be read from device */
		if (mbuf_current >= (MBUF_ELEMENTS-1))
			mbuf_current = 0;

		/* store midi data byte into buffer */
		mpubuffer[mbuf_current] = Read_MPU401(mpu_device->addrport,
			UARTDATA, mpu_device->workarounds);
		mbuf_current++; /* pointer to next blank byte */
		mbuf_bytes++; /* increment count of midi data bytes */

		release_sem_etc(mpu_device->readsemaphore, 1, B_DO_NOT_RESCHEDULE);

		return TRUE; //B_INVOKE_SCHEDULER
	}

	/* No midi data from this interrupt */
	return FALSE; //B_UNHANDLED_INTERRUPT
}


/*-----------------------------------------------------------------*/

uchar
Read_MPU401(unsigned int addrport, const char cmdtype,
	unsigned int workarounds)
{
	uchar mpudatabyte;
	cpu_status status;
	unsigned int regptr;

	/* Only for deep debugging..will slow things down */
	//PRINT(("read workaround 0x%x\n",workarounds));
	switch (workarounds) {
		case 0x11020004: /* Creative Audigy Gameport */
			regptr = (((I_MPU1 + cmdtype) << 16) & PTR_ADDRESS_MASK);
			status = lock();
			gPCI->write_io_32(addrport + D_PTR, regptr);  /*DATA or CMD */
			mpudatabyte = gPCI->read_io_32(addrport + D_DATA);
			unlock(status);
			break;

		case 0x11020005:  /* Creative Audigy LiveDrive */
			regptr = (((I_MPU2 + cmdtype) << 16) & PTR_ADDRESS_MASK);
			status = lock();
			gPCI->write_io_32(addrport + D_PTR, regptr);  /*DATA2 or CMD2 */
			mpudatabyte = gPCI->read_io_32(addrport + D_DATA);
			unlock(status);
			break;

		case 0x14121712:
			status = lock();
			mpudatabyte = gPCI->read_io_8(addrport + cmdtype);
			unlock(status);
			break;

		default:
			mpudatabyte = gISA->read_io_8(addrport + cmdtype);
			break;
	}
	return mpudatabyte;
}


status_t
Write_MPU401(unsigned int addrport, const char cmdtype,
	unsigned int workarounds, uchar mpudatabyte)
{
	cpu_status status;
	unsigned int regptr;

	/* Only for deep debugging..will slow things down */
	//PRINT(("write workaround 0x%x at addr: 0x%x\n",workarounds,addrport));
	switch (workarounds) {
		case 0x11020004: /* Creative Audigy Gameport */
			regptr = (((I_MPU1 + cmdtype) << 16) & PTR_ADDRESS_MASK);
			status = lock();
			gPCI->write_io_32(addrport + D_PTR, regptr); /*DATA or CMD */
			gPCI->write_io_32(addrport + D_DATA, mpudatabyte);
			unlock(status);
			break;

		case 0x11020005: /* Creative Audigy LiveDrive */
			regptr = (((I_MPU2 + cmdtype) << 16) & PTR_ADDRESS_MASK);
			status = lock();
			gPCI->write_io_32(addrport + D_PTR, regptr); /*DATA2 or CMD2 */
			gPCI->write_io_32(addrport + D_DATA, mpudatabyte);
			unlock(status);
			break;

		case 0x14121712:
			status = lock();
			gPCI->write_io_8(addrport + cmdtype, mpudatabyte);
			unlock(status);
			break;

		default:
			gISA->write_io_8(addrport + cmdtype, mpudatabyte);
			break;
	}
	return B_OK;
}


/*-----------------------------------------------------------------*/

static status_t
std_ops(int32 op, ...)
{
	switch(op) {

	case B_MODULE_INIT:

		LOG_CREATE();
		PRINT(("B_MODULE_INIT\n"));

		if (get_module(B_ISA_MODULE_NAME, (module_info **)&gISA) < B_OK)
			return B_ERROR;
 		if (get_module(B_PCI_MODULE_NAME, (module_info **)&gPCI) < B_OK)
			return B_ERROR;
		return B_OK;

	case B_MODULE_UNINIT:
		put_module(B_ISA_MODULE_NAME);
		put_module(B_PCI_MODULE_NAME);
		PRINT(("B_MODULE_UNINIT\n"));
		return B_OK;

	default:
		return B_ERROR;
	}
}

static generic_mpu401_module mpu401_module =
{
	{
		B_MPU_401_MODULE_NAME,
		B_KEEP_LOADED /*0*/ ,
		std_ops
	},
	create_device,
	delete_device,
	midi_open,
	midi_close,
	midi_free,
	midi_control,
	midi_read,
	midi_write,
	interrupt_hook
};

// Module v2 seems to be undocumented
static generic_mpu401_module mpu401_module2 =
{
	{
		"generic/mpu401/v2",
		0,
		std_ops
	},
	create_device_v2,
	delete_device,
	midi_open,
	midi_close,
	midi_free,
	midi_control,
	midi_read,
	midi_write,
	interrupt_hook
};

_EXPORT generic_mpu401_module *modules[] =
{
	&mpu401_module,
	&mpu401_module2,
	NULL
};

spinlock locked = B_SPINLOCK_INITIALIZER;
cpu_status
lock(void)
{
	cpu_status status = disable_interrupts();
	acquire_spinlock(&locked);
	return status;
}

void
unlock(cpu_status status)
{
	 release_spinlock(&locked);
	 restore_interrupts(status);
}