summaryrefslogtreecommitdiff
path: root/drivers/alsa/hdsp.c
blob: cf28a91ab20384c5068ed4748bd693735317a906 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/*
    Copyright (C) 2001 Paul Davis 
    Copyright (C) 2002 Dave LaRose

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "hardware.h"
#include "alsa_driver.h"
#include "hdsp.h"
#include "internal.h"

/* Constants to make working with the hdsp matrix mixer easier */
static const int HDSP_MINUS_INFINITY_GAIN = 0;
static const int HDSP_UNITY_GAIN = 32768;
static const int HDSP_MAX_GAIN = 65535;

/*
 * Use these two arrays to choose the value of the input_channel 
 * argument to hsdp_set_mixer_gain().  hdsp_physical_input_index[n] 
 * selects the nth optical/analog input.  audio_stream_index[n] 
 * selects the nth channel being received from the host via pci/pccard.
 */
static const int hdsp_num_input_channels = 52;
static const int hdsp_physical_input_index[] = {
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};
static const int hdsp_audio_stream_index[] = {
  26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
  38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};

/*
 * Use this array to choose the value of the output_channel 
 * argument to hsdp_set_mixer_gain().  hdsp_physical_output_index[26]
 * and hdsp_physical_output_index[27] refer to the two "line out"
 * channels (1/4" phone jack on the front of digiface/multiface).
 */
static const int hdsp_num_output_channels = 28;
static const int hdsp_physical_output_index[] = {
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};


/* Function for checking argument values */
static int clamp_int(int value, int lower_bound, int upper_bound)
{
  if(value < lower_bound) {
    return lower_bound;
  }
  if(value > upper_bound) {
    return upper_bound;
  }
  return value;
}

/* Note(XXX): Maybe should share this code with hammerfall.c? */
static void 
set_control_id (snd_ctl_elem_id_t *ctl, const char *name)
{
	snd_ctl_elem_id_set_name (ctl, name);
	snd_ctl_elem_id_set_numid (ctl, 0);
	snd_ctl_elem_id_set_interface (ctl, SND_CTL_ELEM_IFACE_HWDEP);
	snd_ctl_elem_id_set_device (ctl, 0);
	snd_ctl_elem_id_set_subdevice (ctl, 0);
	snd_ctl_elem_id_set_index (ctl, 0);
}

/* The hdsp matrix mixer lets you connect pretty much any input to */
/* any output with gain from -inf to about +2dB. Pretty slick. */
/* This routine makes a convenient way to set the gain from */
/* input_channel to output_channel (see hdsp_physical_input_index */
/* etc. above. */
/* gain is an int from 0 to 65535, with 0 being -inf gain, and */
/* 65535 being about +2dB. */

static int hdsp_set_mixer_gain(jack_hardware_t *hw, int input_channel,
			       int output_channel, int gain)
{
	hdsp_t *h = (hdsp_t *) hw->private;
	snd_ctl_elem_value_t *ctl;
	snd_ctl_elem_id_t *ctl_id;
	int err;

	/* Check args */
	input_channel = clamp_int(input_channel, 0, hdsp_num_input_channels);
	output_channel = clamp_int(output_channel, 0, hdsp_num_output_channels);
	gain = clamp_int(gain, HDSP_MINUS_INFINITY_GAIN, HDSP_MAX_GAIN);

	/* Allocate control element and select "Mixer" control */
	snd_ctl_elem_value_alloca (&ctl);
	snd_ctl_elem_id_alloca (&ctl_id);
	set_control_id (ctl_id, "Mixer");
	snd_ctl_elem_value_set_id (ctl, ctl_id);

	/* Apparently non-standard and unstable interface for the */
        /* mixer control. */
	snd_ctl_elem_value_set_integer (ctl, 0, input_channel);
	snd_ctl_elem_value_set_integer (ctl, 1, output_channel);
	snd_ctl_elem_value_set_integer (ctl, 2, gain);

	/* Commit the mixer value and check for errors */
	if ((err = snd_ctl_elem_write (h->driver->ctl_handle, ctl)) != 0) {
	  jack_error ("ALSA/HDSP: cannot set mixer gain (%s)", snd_strerror (err));
	  return -1;
	}

	/* Note (XXX): Perhaps we should maintain a cache of the current */
	/* mixer values, since it's not clear how to query them from the */
	/* hdsp hardware.  We'll leave this out until a little later. */
	return 0;
}
  
static int hdsp_set_input_monitor_mask (jack_hardware_t *hw, unsigned long mask)
{
	int i;

	/* For each input channel */
	for (i = 0; i < 26; i++) {
		/* Monitoring requested for this channel? */
		if(mask & (1<<i)) {
			/* Yes.  Connect physical input to output */

			if(hdsp_set_mixer_gain (hw, hdsp_physical_input_index[i],
						hdsp_physical_output_index[i],
						HDSP_UNITY_GAIN) != 0) {
			  return -1;
			}

#ifdef CANNOT_HEAR_SOFTWARE_STREAM_WHEN_MONITORING
			/* ...and disconnect the corresponding software */
			/* channel */
			if(hdsp_set_mixer_gain (hw, hdsp_audio_stream_index[i],
						hdsp_physical_output_index[i],
						HDSP_MINUS_INFINITY_GAIN) != 0) {
			  return -1;
			}
#endif

		} else {
			/* No.  Disconnect physical input from output */
			if(hdsp_set_mixer_gain (hw, hdsp_physical_input_index[i],
						hdsp_physical_output_index[i],
						HDSP_MINUS_INFINITY_GAIN) != 0) {
			  return -1;
			}

#ifdef CANNOT_HEAR_SOFTWARE_STREAM_WHEN_MONITORING
			/* ...and connect the corresponding software */
			/* channel */
			if(hdsp_set_mixer_gain (hw, hdsp_audio_stream_index[i],
						hdsp_physical_output_index[i],
						HDSP_UNITY_GAIN) != 0) {
			  return -1;
			}
#endif
		}
	}
	/* Cache the monitor mask */
	hw->input_monitor_mask = mask;
	return 0;
}


static int hdsp_change_sample_clock (jack_hardware_t *hw, SampleClockMode mode) 
{
  // Empty for now, until Dave understands more about clock sync so
  // he can test.
  return -1;
}

static double hdsp_get_hardware_peak (jack_port_t *port, jack_nframes_t frame)
{
	return 0;
}

static double hdsp_get_hardware_power (jack_port_t *port, jack_nframes_t frame)
{
	return 0;
}

static void
hdsp_release (jack_hardware_t *hw)
{
	hdsp_t *h = (hdsp_t *) hw->private;

	if (h != 0) {
	  free (h);
	}
}

/* Mostly copied directly from hammerfall.c */
jack_hardware_t *
jack_alsa_hdsp_hw_new (alsa_driver_t *driver)

{
	jack_hardware_t *hw;
	hdsp_t *h;

	hw = (jack_hardware_t *) malloc (sizeof (jack_hardware_t));

	/* Not using clock lock-sync-whatever in home hardware setup */
	/* yet.  Will write this code when can test it. */
	/* hw->capabilities = Cap_HardwareMonitoring|Cap_AutoSync|Cap_WordClock|Cap_ClockMaster|Cap_ClockLockReporting; */
	hw->capabilities = Cap_HardwareMonitoring | Cap_HardwareMetering;
	hw->input_monitor_mask = 0;
	hw->private = 0;

	hw->set_input_monitor_mask = hdsp_set_input_monitor_mask;
	hw->change_sample_clock = hdsp_change_sample_clock;
	hw->release = hdsp_release;
	hw->get_hardware_peak = hdsp_get_hardware_peak;
	hw->get_hardware_power = hdsp_get_hardware_power;
	
	h = (hdsp_t *) malloc (sizeof (hdsp_t));
	h->driver = driver;
	hw->private = h;

	return hw;
}