Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e265d597 authored by JaeHun Jung's avatar JaeHun Jung Committed by Greg Kroah-Hartman
Browse files

ANDROID: sound: usb: Add vendor's hooking interface



In mobile, a co-processor can be used with USB audio to improve power
consumption.  To support this type of hardware, hooks need to be added
to the USB audio subsystem to be able to call into the hardware when
needed.

The main operation of the call-backs are:
  - Initialize the co-processor by transmitting data when initializing.
  - Change the co-processor setting value through the interface
    function.
  - Configure sampling rate
  - pcm open/close
  - other housekeeping

Known issues:
  - This only supports one set of callback hooks, meaning that this only
    works if there is one type of USB controller in the system.  This
    should be changed to be a per-host-controller interface instead of
    one global set of callbacks.

Bug: 156315379
Change-Id: I32e1dd408e64aaef68ee06c480c4b4d4c95546dc
Signed-off-by: default avatarJaeHun Jung <jh0801.jung@samsung.com>
[rework api to be a bit more self-contained and obvious - gregkh]
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent bb0ed735
Loading
Loading
Loading
Loading
+121 −0
Original line number Diff line number Diff line
@@ -106,6 +106,119 @@ static DEFINE_MUTEX(register_mutex);
static struct snd_usb_audio *usb_chip[SNDRV_CARDS];
static struct usb_driver usb_audio_driver;

static struct snd_usb_audio_vendor_ops *usb_vendor_ops;

int snd_vendor_set_ops(struct snd_usb_audio_vendor_ops *ops)
{
	if ((!ops->connect) ||
	    (!ops->disconnect) ||
	    (!ops->set_interface) ||
	    (!ops->set_rate) ||
	    (!ops->set_pcm_buf) ||
	    (!ops->set_pcm_intf) ||
	    (!ops->set_pcm_connection) ||
	    (!ops->set_pcm_binterval) ||
	    (!ops->usb_add_ctls))
		return -EINVAL;

	usb_vendor_ops = ops;
	return 0;
}
EXPORT_SYMBOL_GPL(snd_vendor_set_ops);

struct snd_usb_audio_vendor_ops *snd_vendor_get_ops(void)
{
	return usb_vendor_ops;
}

static int snd_vendor_connect(struct usb_interface *intf)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		return ops->connect(intf);
	return 0;
}

static void snd_vendor_disconnect(struct usb_interface *intf)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		ops->disconnect(intf);
}

int snd_vendor_set_interface(struct usb_device *udev,
			     struct usb_host_interface *intf,
			     int iface, int alt)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		return ops->set_interface(udev, intf, iface, alt);
	return 0;
}

int snd_vendor_set_rate(struct usb_interface *intf, int iface, int rate,
			int alt)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		return ops->set_rate(intf, iface, rate, alt);
	return 0;
}

int snd_vendor_set_pcm_buf(struct usb_device *udev, int iface)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		ops->set_pcm_buf(udev, iface);
	return 0;
}

int snd_vendor_set_pcm_intf(struct usb_interface *intf, int iface, int alt,
			    int direction)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		return ops->set_pcm_intf(intf, iface, alt, direction);
	return 0;
}

int snd_vendor_set_pcm_connection(struct usb_device *udev,
				  enum snd_vendor_pcm_open_close onoff,
				  int direction)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		return ops->set_pcm_connection(udev, onoff, direction);
	return 0;
}

int snd_vendor_set_pcm_binterval(struct audioformat *fp,
				 struct audioformat *found,
				 int *cur_attr, int *attr)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		return ops->set_pcm_binterval(fp, found, cur_attr, attr);
	return 0;
}

static int snd_vendor_usb_add_ctls(struct snd_usb_audio *chip)
{
	struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops();

	if (ops)
		return ops->usb_add_ctls(chip);
	return 0;
}

struct snd_usb_substream *find_snd_usb_substream(unsigned int card_num,
	unsigned int pcm_idx, unsigned int direction, struct snd_usb_audio
	**uchip, void (*disconnect_cb)(struct snd_usb_audio *chip))
@@ -645,6 +758,10 @@ static int usb_audio_probe(struct usb_interface *intf,
	if (err < 0)
		return err;

	err = snd_vendor_connect(intf);
	if (err)
		return err;

	/*
	 * found a config.  now register to ALSA
	 */
@@ -700,6 +817,8 @@ static int usb_audio_probe(struct usb_interface *intf,
	}
	dev_set_drvdata(&dev->dev, chip);

	snd_vendor_usb_add_ctls(chip);

	/*
	 * For devices with more than one control interface, we assume the
	 * first contains the audio controls. We might need a more specific
@@ -775,6 +894,8 @@ static void usb_audio_disconnect(struct usb_interface *intf)
	if (chip->disconnect_cb)
		chip->disconnect_cb(chip);

	snd_vendor_disconnect(intf);

	mutex_lock(&register_mutex);
	if (atomic_inc_return(&chip->shutdown) == 1) {
		struct snd_usb_stream *as;
+17 −0
Original line number Diff line number Diff line
@@ -188,4 +188,21 @@ struct snd_usb_substream *find_snd_usb_substream(unsigned int card_num,
	unsigned int pcm_idx, unsigned int direction, struct snd_usb_audio
	**uchip, void (*disconnect_cb)(struct snd_usb_audio *chip));

int snd_vendor_set_ops(struct snd_usb_audio_vendor_ops *vendor_ops);
struct snd_usb_audio_vendor_ops *snd_vendor_get_ops(void);
int snd_vendor_set_interface(struct usb_device *udev,
			     struct usb_host_interface *alts,
			     int iface, int alt);
int snd_vendor_set_rate(struct usb_interface *intf, int iface, int rate,
			int alt);
int snd_vendor_set_pcm_buf(struct usb_device *udev, int iface);
int snd_vendor_set_pcm_intf(struct usb_interface *intf, int iface, int alt,
			    int direction);
int snd_vendor_set_pcm_connection(struct usb_device *udev,
				  enum snd_vendor_pcm_open_close onoff,
				  int direction);
int snd_vendor_set_pcm_binterval(struct audioformat *fp,
				 struct audioformat *found,
				 int *cur_attr, int *attr);

#endif /* __USBAUDIO_CARD_H */
+5 −0
Original line number Diff line number Diff line
@@ -605,8 +605,13 @@ static int set_sample_rate_v2v3(struct snd_usb_audio *chip, int iface,
	 * interface is active. */
	if (rate != prev_rate) {
		usb_set_interface(dev, iface, 0);

		snd_vendor_set_interface(dev, alts, iface, 0);

		snd_usb_set_interface_quirk(dev);
		usb_set_interface(dev, iface, fmt->altsetting);

		snd_vendor_set_interface(dev, alts, iface, fmt->altsetting);
		snd_usb_set_interface_quirk(dev);
	}

+37 −0
Original line number Diff line number Diff line
@@ -134,6 +134,8 @@ static struct audioformat *find_format(struct snd_usb_substream *subs)
			found = fp;
			cur_attr = attr;
		}

		snd_vendor_set_pcm_binterval(fp, found, &cur_attr, &attr);
	}
	return found;
}
@@ -619,6 +621,10 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
		}
		dev_dbg(&dev->dev, "setting usb interface %d:%d\n",
			fmt->iface, fmt->altsetting);
		err = snd_vendor_set_pcm_intf(iface, fmt->iface,
					      fmt->altsetting, subs->direction);
		if (err)
			return err;
		snd_usb_set_interface_quirk(dev);
	}

@@ -1020,6 +1026,10 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
	struct usb_interface *iface;
	int ret;

	ret = snd_vendor_set_pcm_buf(subs->dev, subs->cur_audiofmt->iface);
	if (ret)
		return ret;

	if (! subs->cur_audiofmt) {
		dev_err(&subs->dev->dev, "no format is specified!\n");
		return -ENXIO;
@@ -1056,6 +1066,17 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
		if (ret < 0)
			goto unlock;

		if (snd_vendor_get_ops()) {
			ret = snd_vendor_set_rate(iface,
						  subs->cur_audiofmt->iface,
						  subs->cur_rate,
						  subs->cur_audiofmt->altsetting);
			if (!ret) {
				subs->need_setup_ep = false;
				goto unlock;
			}
		}

		ret = configure_endpoint(subs);
		if (ret < 0)
			goto unlock;
@@ -1465,6 +1486,11 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
	struct snd_usb_substream *subs = &as->substream[direction];
	int ret;

	ret = snd_vendor_set_pcm_connection(subs->dev, SOUND_PCM_OPEN,
					    direction);
	if (ret)
		return ret;

	subs->interface = -1;
	subs->altset_idx = 0;
	runtime->hw = snd_usb_hardware;
@@ -1493,6 +1519,11 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream)
	struct snd_usb_substream *subs = &as->substream[direction];
	int ret;

	ret = snd_vendor_set_pcm_connection(subs->dev, SOUND_PCM_CLOSE,
					    direction);
	if (ret)
		return ret;

	stop_endpoints(subs, true);
	snd_media_stop_pipeline(subs);

@@ -1500,6 +1531,12 @@ static int snd_usb_pcm_close(struct snd_pcm_substream *substream)
	    subs->interface >= 0 &&
	    !snd_usb_lock_shutdown(subs->stream->chip)) {
		usb_set_interface(subs->dev, subs->interface, 0);
		ret = snd_vendor_set_pcm_intf(usb_ifnum_to_if(subs->dev,
							      subs->interface),
					      subs->interface, 0,
					      direction);
		if (ret)
			return ret;
		subs->interface = -1;
		ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D1);
		snd_usb_unlock_shutdown(subs->stream->chip);
+46 −0
Original line number Diff line number Diff line
@@ -125,4 +125,50 @@ void snd_usb_unlock_shutdown(struct snd_usb_audio *chip);

extern bool snd_usb_use_vmalloc;

struct audioformat;

enum snd_vendor_pcm_open_close {
	SOUND_PCM_CLOSE = 0,
	SOUND_PCM_OPEN,
};

/**
 * struct snd_usb_audio_vendor_ops - function callbacks for USB audio accelerators
 * @connect: called when a new interface is found
 * @disconnect: called when an interface is removed
 * @set_interface: called when an interface is initialized
 * @set_rate: called when the rate is set
 * @set_pcm_buf: called when the pcm buffer is set
 * @set_pcm_intf: called when the pcm interface is set
 * @set_pcm_connection: called when pcm is opened/closed
 * @set_pcm_binterval: called when the pcm binterval is set
 * @usb_add_ctls: called when USB controls are added
 *
 * Set of callbacks for some accelerated USB audio streaming hardware.
 *
 * TODO: make this USB host-controller specific, right now this only works for
 * one USB controller in the system at a time, which is only realistic for
 * self-contained systems like phones.
 */
struct snd_usb_audio_vendor_ops {
	int (*connect)(struct usb_interface *intf);
	void (*disconnect)(struct usb_interface *intf);

	int (*set_interface)(struct usb_device *udev,
			     struct usb_host_interface *alts,
			     int iface, int alt);
	int (*set_rate)(struct usb_interface *intf, int iface, int rate,
			int alt);
	int (*set_pcm_buf)(struct usb_device *udev, int iface);
	int (*set_pcm_intf)(struct usb_interface *intf, int iface, int alt,
			    int direction);
	int (*set_pcm_connection)(struct usb_device *udev,
				  enum snd_vendor_pcm_open_close onoff,
				  int direction);
	int (*set_pcm_binterval)(struct audioformat *fp,
				 struct audioformat *found,
				 int *cur_attr, int *attr);
	int (*usb_add_ctls)(struct snd_usb_audio *chip);
};

#endif /* __USBAUDIO_H */