#include "cmedia_pci.h"
#include "cm_private.h"
#include <string.h>
#include <stdio.h>
#include <KernelExport.h>
#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
#if DO_JOY
extern device_hooks joy_hooks;
#endif
#if DO_PCM
extern device_hooks pcm_hooks;
#endif
#if DO_MUX
extern device_hooks mux_hooks;
#endif
#if DO_MIXER
extern device_hooks mixer_hooks;
#endif
int32 num_cards;
cmedia_pci_dev cards[NUM_CARDS];
int num_names;
char * names[NUM_CARDS * 7 + 1];
uint8
PCI_IO_RD (int offset)
{
return (*pci->read_io_8) (offset);
}
uint32
PCI_IO_RD_32 (int offset)
{
return (*pci->read_io_32) (offset);
}
void
PCI_IO_WR (int offset, uint8 val)
{
(*pci->write_io_8) (offset, val);
}
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)
{
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,
#endif
0x0a, 0x01, 0x01,
0x04, 0x80, 0x80,
0x1a, 0x00, 0x20,
0x1a, 0x00, 0x10,
0x1b, 0x04, 0x04,
0x1a, 0x00, 0x80,
0x24, 0x00, 0x02,
0x24, 0x00, 0x01,
#ifdef DO_MIDI
0x04, 0x04, 0x04,
0x17, 0x00, 0x60,
#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
#if DO_JOY
sprintf(card->joy.name1, "joystick/%s", name);
names[num_names++] = card->joy.name1;
#endif
#if DO_PCM
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
#if DO_MUX
sprintf(card->mux.name, "audio/mux/%s", name);
names[num_names++] = card->mux.name;
#endif
#if DO_MIXER
sprintf(card->mixer.name, "audio/mix/%s", name);
names[num_names++] = card->mixer.name;
#endif
names[num_names] = NULL;
}
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_info ainfo;
ddprintf(("cmedia_pci: testing likely candidate...\n"));
if (get_area_info(curarea, &ainfo)) {
ddprintf(("cmedia_pci: no info\n"));
goto allocate;
}
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);
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;
}
if (trysize > low_size) {
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;
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;
}
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);
return B_OK;
bail6:
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,
#endif
#ifdef DO_MIDI
0x04, 0x00, 0x04,
#endif
};
uchar * ptr = regs;
cpu_status cp;
(*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
#if DO_JOY
if (!strcmp(cards[ix].joy.name1, name)) {
return &joy_hooks;
}
#endif
#if DO_PCM
if (!strcmp(cards[ix].pcm.name, name)) {
return &pcm_hooks;
}
if (!strcmp(cards[ix].pcm.oldname, name)) {
return &pcm_hooks;
}
#endif
#if DO_MUX
if (!strcmp(cards[ix].mux.name, name)) {
return &mux_hooks;
}
#endif
#if DO_MIXER
if (!strcmp(cards[ix].mixer.name, name)) {
return &mixer_hooks;
}
#endif
}
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;
acquire_spinlock(&card->hardware);
status = get_direct(card, 0x10);
#if DEBUG
#endif
#if DO_PCM
if (status & 0x02) {
if (dma_c_interrupt(card)) {
handled = B_INVOKE_SCHEDULER;
}
else {
handled = B_HANDLED_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;
}
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
if (handled == B_UNHANDLED_INTERRUPT) {
if (midi_interrupt(card)) {
handled = B_INVOKE_SCHEDULER;
}
}
release_spinlock(&card->hardware);
restore_interrupts(cp);
return 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);
}
}