/* Copyright 1999, Be Incorporated. All Rights Reserved. This file may be used under the terms of the Be Sample Code License. */ #include "cmedia_pci.h" #include "cm_private.h" #include #include #include #if DEBUG #define KPRINTF(x) kprintf x #else #define KPRINTF(x) #endif EXPORT status_t init_hardware(void); EXPORT status_t init_driver(void); EXPORT void uninit_driver(void); EXPORT const char ** publish_devices(void); EXPORT device_hooks * find_device(const char *); static char pci_name[] = B_PCI_MODULE_NAME; static pci_module_info *pci; static char gameport_name[] = "generic/gameport/v1"; generic_gameport_module * gameport; static char mpu401_name[] = B_MPU_401_MODULE_NAME; generic_mpu401_module * mpu401; #define DO_JOY 1 #define DO_MIDI 1 #define DO_PCM 1 #define DO_MUX 0 #define DO_MIXER 0 #if DO_MIDI extern device_hooks midi_hooks; #endif /* DO_MIDI */ #if DO_JOY extern device_hooks joy_hooks; #endif /* DO_JOY */ #if DO_PCM extern device_hooks pcm_hooks; #endif /* DO_PCM */ #if DO_MUX extern device_hooks mux_hooks; #endif /* DO_MUX */ #if DO_MIXER extern device_hooks mixer_hooks; #endif /* DO_MIXER */ int32 num_cards; cmedia_pci_dev cards[NUM_CARDS]; int num_names; char * names[NUM_CARDS * 7 + 1]; /* vuchar *io_base; */ /* ---------- PCI_IO_RD - read a byte from pci i/o space ----- */ uint8 PCI_IO_RD (int offset) { return (*pci->read_io_8) (offset); } /* ---------- PCI_IO_RD_32 - read a 32 bit value from pci i/o space ----- */ uint32 PCI_IO_RD_32 (int offset) { return (*pci->read_io_32) (offset); } /* ---------- PCI_IO_WR - write a byte to pci i/o space ----- */ void PCI_IO_WR (int offset, uint8 val) { (*pci->write_io_8) (offset, val); } /* detect presence of our hardware */ status_t init_hardware(void) { int ix=0; pci_info info; status_t err = ENODEV; ddprintf(("cmedia_pci: init_hardware()\n")); if (get_module(pci_name, (module_info **)&pci)) return ENOSYS; while ((*pci->get_nth_pci_info)(ix, &info) == B_OK) { if (info.vendor_id == CMEDIA_PCI_VENDOR_ID && (info.device_id == CMEDIA_8338A_DEVICE_ID || info.device_id == CMEDIA_8338B_DEVICE_ID || info.device_id == CMEDIA_8738A_DEVICE_ID || info.device_id == CMEDIA_8738B_DEVICE_ID )) { err = B_OK; } ix++; } #if defined(__POWERPC__) && 0 { char area_name [32]; area_info area; area_id id; sprintf (area_name, "pci_bus%d_isa_io", info.bus); id = find_area (area_name); if (id < 0) err = id; else if ((err = get_area_info (id, &area)) == B_OK) io_base = area.address; } #endif put_module(pci_name); return err; } void set_direct(cmedia_pci_dev * card, int regno, uchar value, uchar mask) { if (mask == 0) { return; } if (mask != 0xff) { uchar old = PCI_IO_RD(card->enhanced+regno); value = (value&mask)|(old&~mask); } PCI_IO_WR(card->enhanced+regno, value); ddprintf(("cmedia_pci: CM%02x = %02x\n", regno, value)); } uchar get_direct(cmedia_pci_dev * card, int regno) { uchar ret = PCI_IO_RD(card->enhanced+regno); return ret; } void set_indirect(cmedia_pci_dev * card, int regno, uchar value, uchar mask) { PCI_IO_WR(card->enhanced+0x23, regno); EIEIO(); if (mask == 0) { return; } if (mask != 0xff) { uchar old = PCI_IO_RD(card->enhanced+0x22); value = (value&mask)|(old&~mask); } PCI_IO_WR(card->enhanced+0x22, value); EIEIO(); ddprintf(("cmedia_pci: CMX%02x = %02x\n", regno, value)); } uchar get_indirect(cmedia_pci_dev * card,int regno) { uchar ret; PCI_IO_WR(card->enhanced+0x23, regno); EIEIO(); ret = PCI_IO_RD(card->enhanced+0x22); return ret; } void dump_card(cmedia_pci_dev* card); #if 0 void dump_card(cmedia_pci_dev* card) { int ix; dprintf("\n"); dprintf("CM: "); for (ix=0; ix<6; ix++) { if (ix == 2 || ix == 3) dprintf(" "); else dprintf(" %02x", get_direct(card, ix)); } for (ix=0; ix<0x32; ix++) { if (!(ix & 7)) { dprintf("\nCMX%02x:", ix); } dprintf(" %02x", get_indirect(card, ix)); } dprintf("\n"); dprintf("\n"); } #else void dump_card(cmedia_pci_dev* card) { } #endif static void disable_card_interrupts(cmedia_pci_dev * card) { set_direct(card, 0x0e, 0x00, 0x03); } static status_t setup_dma(cmedia_pci_dev * card) { /* we're appropriating some ISA space here... */ /* need kernel support to do it right */ const uint16 base = card->enhanced + 0x80; ddprintf(("cmedia_pci: dma base is 0x%04x\n", base)); if (base == 0) return B_DEV_RESOURCE_CONFLICT; card->dma_base = base; return B_OK; } static void set_default_registers(cmedia_pci_dev * card) { static uchar values[] = { #ifdef DO_JOY 0x04, 0x02, 0x02, /* enable joystick */ #endif 0x0a, 0x01, 0x01, /* enable SPDIF inverse before SPDIF_LOOP */ 0x04, 0x80, 0x80, /* enable SPDIF_LOOP */ 0x1a, 0x00, 0x20, /* SPD32SEL disable */ 0x1a, 0x00, 0x10, /* SPDFLOOPI disable */ 0x1b, 0x04, 0x04, /* dual channel mode enable */ 0x1a, 0x00, 0x80, /* Double DAC structure disable */ 0x24, 0x00, 0x02, /* 3D surround disable */ 0x24, 0x00, 0x01, /* disable SPDIF_IN PCM to DAC */ #ifdef DO_MIDI 0x04, 0x04, 0x04, /* enable MPU-401 */ 0x17, 0x00, 0x60, /* default at 0x330 */ #endif }; uchar * ptr = values; while (ptr < values+sizeof(values)) { set_direct(card, ptr[0], ptr[1], ptr[2]); ptr += 3; } } static void make_device_names(cmedia_pci_dev * card) { char * name = card->name; sprintf(name, "cmedia_pci/%ld", card-cards + 1); #if DO_MIDI sprintf(card->midi.name, "midi/%s", name); names[num_names++] = card->midi.name; #endif /* DO_MIDI */ #if DO_JOY sprintf(card->joy.name1, "joystick/%s", name); names[num_names++] = card->joy.name1; #endif /* DO_JOY */ #if DO_PCM /* cmedia_pci DMA doesn't work when physical NULL isn't NULL from PCI */ /* this is a hack to not export bad devices on BeBox hardware */ if ((*pci->ram_address)(0) == 0) { sprintf(card->pcm.name, "audio/raw/%s", name); names[num_names++] = card->pcm.name; sprintf(card->pcm.oldname, "audio/old/%s", name); names[num_names++] = card->pcm.oldname; } #endif /* DO_PCM */ #if DO_MUX sprintf(card->mux.name, "audio/mux/%s", name); names[num_names++] = card->mux.name; #endif /* DO_MUX */ #if DO_MIXER sprintf(card->mixer.name, "audio/mix/%s", name); names[num_names++] = card->mixer.name; #endif /* DO_MIXER */ names[num_names] = NULL; } /* We use the SV chip in ISA DMA addressing mode, which is 24 bits */ /* so we need to find suitable, locked, contiguous memory in that */ /* physical address range. */ static status_t find_low_memory(cmedia_pci_dev * card) { size_t low_size = (MIN_MEMORY_SIZE + (B_PAGE_SIZE - 1)) &~ (B_PAGE_SIZE - 1); physical_entry where; size_t trysize; area_id curarea; void * addr; char name[DEVNAME]; sprintf(name, "%s_low", card->name); if (low_size < MIN_MEMORY_SIZE) { low_size = MIN_MEMORY_SIZE; } trysize = low_size; curarea = find_area(name); if (curarea >= 0) { /* area there from previous run */ area_info ainfo; ddprintf(("cmedia_pci: testing likely candidate...\n")); if (get_area_info(curarea, &ainfo)) { ddprintf(("cmedia_pci: no info\n")); goto allocate; } /* test area we found */ trysize = ainfo.size; addr = ainfo.address; if (trysize < low_size) { ddprintf(("cmedia_pci: too small (%lx)\n", trysize)); goto allocate; } if (get_memory_map(addr, trysize, &where, 1) < B_OK) { ddprintf(("cmedia_pci: no memory map\n")); goto allocate; } if ((where.address & ~(phys_addr_t)0xffffff) != 0) { ddprintf(("cmedia_pci: bad physical address\n")); goto allocate; } if (ainfo.lock < B_FULL_LOCK || where.size < low_size) { ddprintf(("cmedia_pci: lock not contiguous\n")); goto allocate; } dprintf("cmedia_pci: physical %#" B_PRIxPHYSADDR " logical %p\n", where.address, ainfo.address); goto a_o_k; } allocate: if (curarea >= 0) { delete_area(curarea); /* area didn't work */ curarea = -1; } ddprintf(("cmedia_pci: allocating new low area\n")); curarea = create_area(name, &addr, B_ANY_KERNEL_ADDRESS, trysize, B_LOMEM, B_READ_AREA | B_WRITE_AREA); ddprintf(("cmedia_pci: create_area(%lx) returned %lx logical %p\n", trysize, curarea, addr)); if (curarea < 0) { goto oops; } if (get_memory_map(addr, low_size, &where, 1) < 0) { delete_area(curarea); curarea = B_ERROR; goto oops; } ddprintf(("cmedia_pci: physical %p\n", where.address)); if ((where.address & ~(phys_addr_t)0xffffff) != 0) { delete_area(curarea); curarea = B_ERROR; goto oops; } if (((where.address + low_size) & ~(phys_addr_t)0xffffff) != 0) { delete_area(curarea); curarea = B_ERROR; goto oops; } /* hey, it worked! */ if (trysize > low_size) { /* don't waste */ resize_area(curarea, low_size); } oops: if (curarea < 0) { dprintf("cmedia_pci: failed to create low_mem area\n"); return curarea; } a_o_k: ddprintf(("cmedia_pci: successfully found or created low area!\n")); card->low_size = low_size; card->low_mem = addr; card->low_phys = where.address; card->map_low = curarea; return B_OK; } static status_t setup_cmedia_pci(cmedia_pci_dev * card) { status_t err = B_OK; /* cpu_status cp; */ ddprintf(("cmedia_pci: setup_cmedia_pci(%p)\n", card)); if ((card->pcm.init_sem = create_sem(1, "cm pcm init")) < B_OK) goto bail; #if 1 if ((*mpu401->create_device)(0x330, &card->midi.driver, #else if ((*mpu401->create_device)(card->info.u.h0.base_registers[3], &card->midi.driver, #endif 0, midi_interrupt_op, &card->midi) < B_OK) goto bail3; #if 1 if ((*gameport->create_device)(0x201, &card->joy.driver) < B_OK) #else if ((*gameport->create_device)(card->info.u.h0.base_registers[4], &card->joy.driver) < B_OK) #endif goto bail4; ddprintf(("midi %p gameport %p\n", card->midi.driver, card->joy.driver)); card->midi.card = card; err = find_low_memory(card); if (err < B_OK) { goto bail5; } //cp = disable_interrupts(); //acquire_spinlock(&card->hardware); make_device_names(card); card->enhanced = card->info.u.h0.base_registers[0]; ddprintf(("cmedia_pci: %s enhanced at %x\n", card->name, card->enhanced)); ddprintf(("cmedia_pci: revision %x\n", get_indirect(card, 0x15))); disable_card_interrupts(card); if (setup_dma(card) != B_OK) { dprintf("cmedia pci: can't setup DMA\n"); goto bail6; } set_default_registers(card); //release_spinlock(&card->hardware); //restore_interrupts(cp); return B_OK; bail6: // deallocate low memory bail5: (*gameport->delete_device)(card->joy.driver); bail4: (*mpu401->delete_device)(card->midi.driver); bail3: delete_sem(card->pcm.init_sem); bail: return err < B_OK ? err : B_ERROR; } static int debug_cmedia(int argc, char * argv[]) { int ix = 0; if (argc == 2) { ix = parse_expression(argv[1]) - 1; } if (argc > 2 || ix < 0 || ix >= num_cards) { dprintf("cmedia_pci: dude, you gotta watch your syntax!\n"); return -1; } dprintf("%s: enhanced registers at 0x%x\n", cards[ix].name, cards[ix].enhanced); dprintf("%s: open %" B_PRId32 " dma_a at 0x%x dma_c 0x%x\n", cards[ix].pcm.name, cards[ix].pcm.open_count, cards[ix].pcm.dma_a, cards[ix].pcm.dma_c); if (cards[ix].pcm.open_count) { dprintf(" dma_a: 0x%" B_PRIu32 "+0x%" B_PRIu32 " dma_c: 0x%" B_PRIu32 "+0x%" B_PRIu32 "\n", PCI_IO_RD_32((int)cards[ix].pcm.dma_a), PCI_IO_RD_32((int)cards[ix].pcm.dma_a + 4), PCI_IO_RD_32((int)cards[ix].pcm.dma_c), PCI_IO_RD_32((int)cards[ix].pcm.dma_c + 4)); } return 0; } status_t init_driver(void) { pci_info info; int ix = 0; status_t err; num_cards = 0; ddprintf(("cmedia_pci: init_driver()\n")); if (get_module(pci_name, (module_info **)&pci)) return ENOSYS; if (get_module(gameport_name, (module_info **)&gameport)) { put_module(pci_name); return ENOSYS; } ddprintf(("MPU\n")); if (get_module(mpu401_name, (module_info **)&mpu401)) { put_module(gameport_name); put_module(pci_name); return ENOSYS; } ddprintf(("MPU: %p\n", mpu401)); while ((*pci->get_nth_pci_info)(ix, &info) == B_OK) { if (info.vendor_id == CMEDIA_PCI_VENDOR_ID && (info.device_id == CMEDIA_8338A_DEVICE_ID || info.device_id == CMEDIA_8338B_DEVICE_ID || info.device_id == CMEDIA_8738A_DEVICE_ID || info.device_id == CMEDIA_8738B_DEVICE_ID )) { if (num_cards == NUM_CARDS) { dprintf("Too many C-Media cards installed!\n"); break; } memset(&cards[num_cards], 0, sizeof(cmedia_pci_dev)); cards[num_cards].info = info; #ifdef __HAIKU__ if ((err = (*pci->reserve_device)(info.bus, info.device, info.function, DRIVER_NAME, &cards[num_cards])) < B_OK) { dprintf("%s: failed to reserve_device(%d, %d, %d,): %s\n", DRIVER_NAME, info.bus, info.device, info.function, strerror(err)); continue; } #endif if (setup_cmedia_pci(&cards[num_cards])) { dprintf("Setup of C-Media %d failed\n", num_cards + 1); #ifdef __HAIKU__ (*pci->unreserve_device)(info.bus, info.device, info.function, DRIVER_NAME, &cards[num_cards]); #endif } else { num_cards++; } } ix++; } if (!num_cards) { KPRINTF(("no cards\n")); put_module(mpu401_name); put_module(gameport_name); put_module(pci_name); ddprintf(("cmedia_pci: no suitable cards found\n")); return ENODEV; } #if DEBUG add_debugger_command("cmedia", debug_cmedia, "cmedia [card# (1-n)]"); #endif return B_OK; } static void teardown_cmedia_pci(cmedia_pci_dev * card) { static uchar regs[] = { #ifdef DO_JOY 0x04, 0x00, 0x02, /* enable joystick */ #endif #ifdef DO_MIDI 0x04, 0x00, 0x04, /* enable MPU-401 */ #endif }; uchar * ptr = regs; cpu_status cp; /* remove created devices */ (*gameport->delete_device)(card->joy.driver); (*mpu401->delete_device)(card->midi.driver); cp = disable_interrupts(); acquire_spinlock(&card->hardware); while (ptr < regs + sizeof(regs)) { set_direct(card, ptr[0], ptr[1], ptr[2]); ptr += 3; } disable_card_interrupts(card); release_spinlock(&card->hardware); restore_interrupts(cp); delete_sem(card->pcm.init_sem); #ifdef __HAIKU__ (*pci->unreserve_device)(card->info.bus, card->info.device, card->info.function, DRIVER_NAME, card); #endif } void uninit_driver(void) { int ix, cnt = num_cards; num_cards = 0; ddprintf(("cmedia_pci: uninit_driver()\n")); remove_debugger_command("cmedia", debug_cmedia); for (ix = 0; ix < cnt; ix++) { teardown_cmedia_pci(&cards[ix]); } memset(&cards, 0, sizeof(cards)); put_module(mpu401_name); put_module(gameport_name); put_module(pci_name); } const char ** publish_devices(void) { int ix = 0; ddprintf(("cmedia_pci: publish_devices()\n")); for (ix = 0; names[ix]; ix++) { ddprintf(("cmedia_pci: publish %s\n", names[ix])); } return (const char **)names; } device_hooks * find_device(const char * name) { int ix; ddprintf(("cmedia_pci: find_device(%s)\n", name)); for (ix = 0; ix < num_cards; ix++) { #if DO_MIDI if (!strcmp(cards[ix].midi.name, name)) { return &midi_hooks; } #endif /* DO_MIDI */ #if DO_JOY if (!strcmp(cards[ix].joy.name1, name)) { return &joy_hooks; } #endif /* DO_JOY */ #if DO_PCM if (!strcmp(cards[ix].pcm.name, name)) { return &pcm_hooks; } if (!strcmp(cards[ix].pcm.oldname, name)) { return &pcm_hooks; } #endif /* DO_PCM */ #if DO_MUX if (!strcmp(cards[ix].mux.name, name)) { return &mux_hooks; } #endif /* DO_MUX */ #if DO_MIXER if (!strcmp(cards[ix].mixer.name, name)) { return &mixer_hooks; } #endif /* DO_MIXER */ } ddprintf(("cmedia_pci: find_device(%s) failed\n", name)); return NULL; } int32 api_version = B_CUR_DRIVER_API_VERSION; static int32 cmedia_pci_interrupt(void * data) { cpu_status cp = disable_interrupts(); cmedia_pci_dev * card = (cmedia_pci_dev *)data; uchar status; int32 handled = B_UNHANDLED_INTERRUPT; /* KTRACE(); / * */ acquire_spinlock(&card->hardware); status = get_direct(card, 0x10); #if DEBUG /* kprintf("%x\n", status); / * */ #endif #if DO_PCM if (status & 0x02) { if (dma_c_interrupt(card)) { handled = B_INVOKE_SCHEDULER; } else { handled = B_HANDLED_INTERRUPT; } /* acknowledge interrupt */ set_direct(card, 0x0e, 0x00, 0x02); set_direct(card, 0x0e, 0x02, 0x02); } if (status & 0x01) { if (dma_a_interrupt(card)) { handled = B_INVOKE_SCHEDULER; } else { handled = B_HANDLED_INTERRUPT; } /* acknowledge interrupt */ set_direct(card, 0x0e, 0x00, 0x01); set_direct(card, 0x0e, 0x01, 0x01); } #endif #if DO_MIDI status = get_direct(card, 0x12); if (status & 0x01) { if (midi_interrupt(card)) { handled = B_INVOKE_SCHEDULER; } else { handled = B_HANDLED_INTERRUPT; } } #endif /* Sometimes, the Sonic Vibes will receive a byte of Midi data... ** And duly note it in the MPU401 status register... ** And generate an interrupt... ** But not bother setting the midi interrupt bit in the ISR. ** Thanks a lot, S3. */ if (handled == B_UNHANDLED_INTERRUPT) { if (midi_interrupt(card)) { handled = B_INVOKE_SCHEDULER; } } /* KTRACE(); / * */ release_spinlock(&card->hardware); restore_interrupts(cp); return handled; // return (handled == B_INVOKE_SCHEDULER) ? B_HANDLED_INTERRUPT : handled; } void increment_interrupt_handler(cmedia_pci_dev * card) { KPRINTF(("cmedia_pci: increment_interrupt_handler()\n")); if (atomic_add(&card->inth_count, 1) == 0) { // !!! KPRINTF(("cmedia_pci: intline %d int %p\n", card->info.u.h0.interrupt_line, cmedia_pci_interrupt)); install_io_interrupt_handler(card->info.u.h0.interrupt_line, cmedia_pci_interrupt, card, 0); } } void decrement_interrupt_handler(cmedia_pci_dev * card) { KPRINTF(("cmedia_pci: decrement_interrupt_handler()\n")); if (atomic_add(&card->inth_count, -1) == 1) { KPRINTF(("cmedia_pci: remove_io_interrupt_handler()\n")); remove_io_interrupt_handler(card->info.u.h0.interrupt_line, cmedia_pci_interrupt, card); } }