/*
 * ddksample_audio.c: OSS DDK sample driver - Mixer functionality
 *
 * Description:
 * This source module implements the mixer functionality of the sample
 * driver. The devc and portc fields used by the mixer are actually used
 * in the ddksample_misc.c module which emulates some audio hardware.
 */


/*
 * Solaris DDI includes
 */
#include <sys/types.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

/*
 * OSS specific includes
 */
#include <sys/soundcard.h>
#include <sys/ossddk/ossddk.h>

/***************************************************
 * Private types and variables
 */

#include "ddksample.h"

/**************************************************/
static const unsigned char peak_cnv[256] = {
    0, 18, 29, 36, 42, 47, 51, 54, 57, 60, 62, 65, 67, 69, 71, 72,
    74, 75, 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 89, 90,
    91, 92, 93, 93, 94, 95, 95, 96, 97, 97, 98, 99, 99, 100, 100, 101,
    101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108, 108,
    108,
    109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 113, 113, 113, 114, 114,
    114,
    115, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 118, 119,
    119,
    119, 119, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123,
    123,
    123, 123, 124, 124, 124, 124, 125, 125, 125, 125, 125, 126, 126, 126, 126,
    126,
    127, 127, 127, 127, 127, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129,
    130,
    130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 131, 132, 132, 132, 132,
    132,
    132, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 135,
    135,
    135, 135, 135, 135, 135, 136, 136, 136, 136, 136, 136, 136, 137, 137, 137,
    137,
    137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139,
    139,
    139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141,
    141,
    141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 142, 143, 143,
    143,
    143, 143, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 144, 144, 144,
    144,
  };

static int
peak_cb (int dev, int ctl_id, unsigned int cmd, int value)
{
	ddksample_devc *devc=ossddk_mixer_get_devc(dev);

	if (ctl_id < 0 || ctl_id >= devc->num_portc)
	   return -EINVAL;

	if (cmd == SNDCTL_MIX_READ)
	{
		ddksample_portc *portc = &devc->portc[ctl_id];
		int val, left, right;

		/*
		 * Truncate the peak counters to 8 bits (signed range).
		 */
		left = portc->left_peak >> 15;
		right = portc->right_peak >> 15;

		if (left>255)left=255;
		if (right>255)right=255;

		/*
		 * Convert to logarithmic (dB) scale 
		 */
		left = peak_cnv[left];
		right = peak_cnv[right];

		val = left | (right<<8);

		portc->left_peak=portc->right_peak=0; /* Reset */

		return val;
	}


	return -EINVAL;
}

static int
mute_cb (int dev, int ctl_id, unsigned int cmd, int value)
{
	ddksample_devc *devc=ossddk_mixer_get_devc(dev);

	if (ctl_id < 0 || ctl_id >= devc->num_portc)
	   return -EINVAL;

	if (cmd == SNDCTL_MIX_WRITE)
	{
		ddksample_portc *portc = &devc->portc[ctl_id];

		return portc->mute = (value != 0);
	}

	if (cmd == SNDCTL_MIX_READ)
	{
		ddksample_portc *portc = &devc->portc[ctl_id];

		return portc->mute;
	}

	return -EINVAL;
}

static int
gain_cb (int dev, int ctl_id, unsigned int cmd, int value)
{
	ddksample_devc *devc=ossddk_mixer_get_devc(dev);

	if (ctl_id < 0 || ctl_id >= devc->num_portc)
	   return -EINVAL;

	if (cmd == SNDCTL_MIX_WRITE)
	{
		int left, right;
		ddksample_portc *portc = &devc->portc[ctl_id];

		left = value & 0xff;
		right = (value >> 8) & 0xff;

		if (left<0)left=0;
		if (left>DDKSAMPLE_MAX_VOL)left=DDKSAMPLE_MAX_VOL;
		if (right<0)right=0;
		if (right>DDKSAMPLE_MAX_VOL)right=DDKSAMPLE_MAX_VOL;

		portc->left_volume=left;
		portc->right_volume=right;

		return left | (right << 8);
	}

	if (cmd == SNDCTL_MIX_READ)
	{
		ddksample_portc *portc = &devc->portc[ctl_id];
		return portc->left_volume | (portc->right_volume << 8);
	}

	return -EINVAL;
}

static int
ddksample_create_controls(int dev)
{
	ddksample_devc *devc=ossddk_mixer_get_devc(dev);
	int group, top, i;
	int ctl;

	/*
	 * Create the top level group
	 *
	 * By convention all controls specific to /dev/dsp# device files
	 * are grouped under a group called "/dev".
	 */

	if ((top=ossddk_mixer_create_group(
				dev, 			// Mixer device number
				OSSDDK_MIXER_ROOT, 	// Parent group
				"/dev"))<0)		// Name
	   return top;

	for (i=0;i<devc->num_portc;i++)
	{
		char name[16];

		ddksample_portc *portc = &devc->portc[i];

	/*
	 * Create a special group for each /dev/dsp# device.
	 * Control/group names staring with "@dsp" have special function.
	 * GUI mixers will replace them with the application name using
	 * the corresponding /dev/dsp# device. In this way the mixer
	 * interface is more intuitive.
	 *
	 * In our case we create a group for each audio device and
	 * place the controls under them. 
	 *
	 * We have both a volume slider and a peak meter for each device
	 * file. The convention is that the volume slider will be shown first
	 * if it affects the peak meter (such as in case). Otherwise the
	 * peak meter will be first.
	 */
		sprintf(name, "@dsp%d", portc->dev);

		if ((group=ossddk_mixer_create_group(
				dev, 		// Mixer device number
				top, 		// Parent group
				name))<0)	// Name
		   return group;

      		if ((ctl = ossddk_mixer_create_control (
				dev,			// Mixer device number
				group,			// Parent group
				i,			// Controller id
				gain_cb,		// Callback function
				MIXT_STEREOSLIDER,	// Control type
				"-", 			// Name (no name)
				DDKSAMPLE_MAX_VOL,	// Maximum value
				MIXF_READABLE|MIXF_WRITEABLE)) < 0)	// Protection
			return ctl;

      		if ((ctl = ossddk_mixer_create_control (
				dev,		// Mixer device number
				group,		// Parent group
				i,		// Controller id
				peak_cb,	// Callback function
				MIXT_STEREOPEAK,// Control type
				"-", 		// Name (no name)
				144,		// Maximum value
				MIXF_READABLE)) < 0)	// Protection
			return ctl;

      		if ((ctl = ossddk_mixer_create_control (
				dev,		// Mixer device number
				group,		// Parent group
				i,		// Controller id
				mute_cb,	// Callback function
				MIXT_ONOFF,	// Control type
				"MUTE",		// Name
				2,		// ONOFF controls should have 2 here
				MIXF_READABLE|MIXF_WRITEABLE)) < 0)	// Protection
			return ctl;
	}

	return OSSDDK_OK;
}

/**************************************************/

static int
ddksample_legacy_mixer_ioctl (int dev, int audiodev, unsigned int cmd, int *arg)
{
	int vol, left, right;
	ddksample_devc *devc=ossddk_mixer_get_devc(dev);

	switch (cmd)
	{
	case SOUND_MIXER_READ_DEVMASK:
	case SOUND_MIXER_READ_STEREODEVS:
		*arg = SOUND_MASK_VOLUME|SOUND_MASK_MIC|SOUND_MASK_RECLEV;
		return OSSDDK_OK;
		break;

	case SOUND_MIXER_READ_RECMASK:
	case SOUND_MIXER_READ_RECSRC:
	case SOUND_MIXER_WRITE_RECSRC:
		*arg=SOUND_MASK_MIC;
		return OSSDDK_OK;
		break;

	case SOUND_MIXER_READ_VOLUME:
		*arg=devc->mainvol_left | (devc->mainvol_right << 8);
		return OSSDDK_OK;
		break;

	case SOUND_MIXER_WRITE_VOLUME:
		vol=*arg;
		left = vol & 0xff;
		right = (vol >> 8) & 0xff;

		if (left < 0)left=0;
		if (left > 100)left=100;
		if (right < 0)right=0;
		if (right > 100)right=100;

		devc->mainvol_left=left;
		devc->mainvol_right=right;

		*arg=devc->mainvol_left | (devc->mainvol_right << 8);
		return OSSDDK_OK;
		break;

	case SOUND_MIXER_READ_MIC:
	case SOUND_MIXER_WRITE_MIC:
		*arg=100 | (100<<8); /* Always full volume */
		return OSSDDK_OK;
		break;

	case SOUND_MIXER_READ_RECLEV:
	case SOUND_MIXER_WRITE_RECLEV:
		*arg=100 | (100<<8); /* Always full volume */
		return OSSDDK_OK;
		break;

	default:
		return -EINVAL;
	}
}

static mixer_driver_t ddksample_mixer_driver = {
  ddksample_legacy_mixer_ioctl,
  MAX_SUBDEVICE * 4,			/* Number of mixer elements we need */
  ddksample_create_controls
};

int
ddksample_mixer_init(ddksample_devc *devc)
{
	devc->mainvol_left=100;
	devc->mainvol_right=100;

	if ((devc->mixer_dev=ossddk_install_mixer(
				  OSS_MIXER_DRIVER_VERSION,
				     devc->osdev,
				     devc->osdev,
				     "OSS DDK sample mixer",
				     &ddksample_mixer_driver,
				     sizeof (mixer_driver_t),
				     devc)) >= 0)
	   return devc->mixer_dev; /* Error code */

#if 0
	/*
	 * By default OSS will delay creation of the mixer controls
	 * until the mixer is actually used by some application. This means
	 * that in some cases the mixer doesn't get created at all before
	 * the driver gets unloaded again.
	 *
	 * The driver can force the mixer to be created and the creation
	 * callback (ddksample_create_controls in our case) to be called
	 * immediately by calling ossddk_mixer_touch(). This is necessary only
	 * if the creation function performs initializations that must be
	 * done immediately. Otherwise the touch_mixer() routine must not be
	 * called.
	 *
	 * In this driver ossddk_mixer_touch() must not be called. Mixer
	 * initialization is designed to be invoked after audio devices are
	 * initialized. Otherwise devc->num_portc will not have right value
	 * and mixer initialization will fail.
	 */
	ossddk_mixer_touch(devc->mixer_dev);
#endif

	return OSSDDK_OK;
}
