/*
	Copyright 1999, Be Incorporated.   All Rights Reserved.
	This file may be used under the terms of the Be Sample Code License.
*/

#include "sv_private.h"
#include <string.h>

#if !defined(_KERNEL_EXPORT_H)
#include <KernelExport.h>
#endif /* _KERNEL_EXPORT_H */


static status_t mux_open(const char *name, uint32 flags, void **cookie);
static status_t mux_close(void *cookie);
static status_t mux_free(void *cookie);
static status_t mux_control(void *cookie, uint32 op, void *data, size_t len);
static status_t mux_read(void *cookie, off_t pos, void *data, size_t *len);
static status_t mux_write(void *cookie, off_t pos, const void *data, size_t *len);

device_hooks mux_hooks = {
    &mux_open,
    &mux_close,
    &mux_free,
    &mux_control,
    &mux_read,
    &mux_write,
    NULL,		/* select */
    NULL,		/* deselect */
    NULL,		/* readv */
    NULL		/* writev */
};


typedef struct {
	int port_l;
	int port_r;
	int minval;
	int maxval;
	int leftshift;
	int mask;
	char name[B_OS_NAME_LENGTH];
} mux_info;

const mux_info the_muxes[] = {
	{ 0, 1, 1, 7, 5, 0xe0, "Sampling Input" },
	{ 0, -1, 0, 1, 4, 0x10, "Mic +20dB Selection" },
	{ 0x2a, -1, 0, 1, 0, 0x01, "MIDI Output To Synth" },
	{ 0x2a, -1, 0, 1, 1, 0x02, "MIDI Input To Synth" },
	{ 0x2a, -1, 0, 1, 2, 0x04, "MIDI Output To Port" },
};

static const uchar unmap_input[] = {
  0,
  SONIC_VIBES_INPUT_CD,
  SONIC_VIBES_INPUT_DAC,
  SONIC_VIBES_INPUT_AUX2,
  SONIC_VIBES_INPUT_LINE,
  SONIC_VIBES_INPUT_AUX1,
  SONIC_VIBES_INPUT_MIC,
  SONIC_VIBES_INPUT_MIX_OUT
};

static uchar
map_input(uchar val) {
  int i;
  for (i = 0; i < 8; i++)
	if (unmap_input[i] == val)
	  return i;
  return 0;
}


static status_t
mux_open(
	const char * name,
	uint32 flags,
	void ** cookie)
{
	int ix;
	/* mux_dev * plex = NULL; */

	ddprintf(("sonic_vibes: mux_open()\n"));

	*cookie = NULL;
	for (ix=0; ix<num_cards; ix++) {
		if (!strcmp(name, cards[ix].mux.name)) {
			break;
		}
	}
	if (ix == num_cards) {
		return ENODEV;
	}

	atomic_add(&cards[ix].mux.open_count, 1);
	cards[ix].mux.card = &cards[ix];
	*cookie = &cards[ix].mux;

	return B_OK;
}


static status_t
mux_close(
	void * cookie)
{
	mux_dev * plex = (mux_dev *)cookie;

	atomic_add(&plex->open_count, -1);

	return B_OK;
}


static status_t
mux_free(
	void * cookie)
{
	ddprintf(("sonic_vibes: mux_free()\n"));

	if (((mux_dev *)cookie)->open_count != 0) {
		dprintf("sonic_vibes: mux open_count is bad in mux_free()!\n");
	}
	return B_OK;	/* already done in close */
}


static int
get_mux_value(
	sonic_vibes_dev * card,
	int ix)
{
	uchar val;
	if (ix < 0) {
		return -1;
	}
	if (ix > 4) {
		return -1;
	}
	val = get_indirect(card, the_muxes[ix].port_l);
	val &= the_muxes[ix].mask;
	val >>= the_muxes[ix].leftshift;
	if (ix == SONIC_VIBES_INPUT_MUX)
	  return unmap_input[val];
	return val;
}


static int
gather_info(
	mux_dev * mux,
	sonic_vibes_routing * data,
	int count)
{
	int ix;
	cpu_status cp;

	cp = disable_interrupts();
	acquire_spinlock(&mux->card->hardware);

	for (ix=0; ix<count; ix++) {
		data[ix].value = get_mux_value(mux->card, data[ix].selector);
		if (data[ix].value < 0) {
			break;
		}
	}

	release_spinlock(&mux->card->hardware);
	restore_interrupts(cp);

	return ix;
}


static status_t
set_mux_value(
	sonic_vibes_dev * card,
	int selector,
	int value)
{
	ddprintf(("set_mux_value(%d,%d)\n", selector, value));
	if (selector < 0 || selector > 4) {
		ddprintf(("selector EINVAL\n"));
		return EINVAL;
	}
	if (selector == SONIC_VIBES_INPUT_MUX)
	    value = map_input(value);
	if (value < the_muxes[selector].minval ||
		value > the_muxes[selector].maxval) {
		ddprintf(("value EINVAL\n"));
		return EINVAL;
	}
	set_indirect(card, the_muxes[selector].port_l, 
		(value << the_muxes[selector].leftshift),
		the_muxes[selector].mask);
	if (the_muxes[selector].port_r > -1) {
		set_indirect(card, the_muxes[selector].port_r, 
			(value << the_muxes[selector].leftshift),
			the_muxes[selector].mask);
	}
	return B_OK;
}


static int
disperse_info(
	mux_dev * mux,
	sonic_vibes_routing * data,
	int count)
{
	int ix;
	cpu_status cp;

	cp = disable_interrupts();
	acquire_spinlock(&mux->card->hardware);

	for (ix=0; ix<count; ix++) {
		if (set_mux_value(mux->card, data[ix].selector, data[ix].value) < B_OK) {
			break;
		}
	}

	release_spinlock(&mux->card->hardware);
	restore_interrupts(cp);

	return ix;
}


static status_t
mux_control(
	void * cookie,
	uint32 iop,
	void * data,
	size_t len)
{
	mux_dev * plex = (mux_dev *)cookie;
	status_t err = B_OK;

	ddprintf(("sonic_vibes: mux_control()\n")); /* slow printing */

	switch (iop) {
	case B_ROUTING_GET_VALUES:
		((sonic_vibes_routing_cmd *)data)->count = 
			gather_info(plex, ((sonic_vibes_routing_cmd *)data)->data, 
				((sonic_vibes_routing_cmd *)data)->count);
		break;
	case B_ROUTING_SET_VALUES:
		((sonic_vibes_routing_cmd *)data)->count = 
			disperse_info(plex, ((sonic_vibes_routing_cmd *)data)->data, 
				((sonic_vibes_routing_cmd *)data)->count);
		break;
	default:
		err = B_BAD_VALUE;
		break;
	}
	return err;
}


static status_t
mux_read(
	void * cookie,
	off_t pos,
	void * data,
	size_t * nread)
{
	return EPERM;
}


static status_t
mux_write(
	void * cookie,
	off_t pos,
	const void * data,
	size_t * nwritten)
{
	return EPERM;
}

