/* * 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 #include #include #include #include #include #include #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); }