From 1eb185541df90c3f790676b9ac362683de3519f2 Mon Sep 17 00:00:00 2001 From: JaeHun Jung Date: Tue, 26 May 2020 16:53:38 +0900 Subject: [PATCH] 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: JaeHun Jung [rework api to be a bit more self-contained and obvious - gregkh] Signed-off-by: Greg Kroah-Hartman --- sound/usb/card.c | 121 +++++++++++++++++++++++++++++++++++++++++++ sound/usb/card.h | 17 ++++++ sound/usb/clock.c | 5 ++ sound/usb/pcm.c | 37 +++++++++++++ sound/usb/usbaudio.h | 46 ++++++++++++++++ 5 files changed, 226 insertions(+) diff --git a/sound/usb/card.c b/sound/usb/card.c index 1d9d0baf82cb..c4bcb3434b12 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -112,6 +112,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)) @@ -666,6 +779,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 */ @@ -727,6 +844,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 @@ -815,6 +934,8 @@ static void usb_audio_disconnect(struct usb_interface *intf) if (chip->disconnect_cb) chip->disconnect_cb(chip); + snd_vendor_disconnect(intf); + mutex_lock(®ister_mutex); if (atomic_inc_return(&chip->shutdown) == 1) { struct snd_usb_stream *as; diff --git a/sound/usb/card.h b/sound/usb/card.h index 4b1728983efc..385eb0adf600 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -185,4 +185,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 */ diff --git a/sound/usb/clock.c b/sound/usb/clock.c index b118cf97607f..9a97d7876f39 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -642,8 +642,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); } diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 077c8d121bf8..1947ea919036 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -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; } @@ -642,6 +644,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); } @@ -1040,6 +1046,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; @@ -1073,6 +1083,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; @@ -1482,6 +1503,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; @@ -1510,12 +1536,23 @@ 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; + snd_media_stop_pipeline(subs); if (!as->chip->keep_iface && 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); diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 04ca1ef77a87..e7e35a326ce2 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -129,4 +129,50 @@ void snd_usb_unlock_shutdown(struct snd_usb_audio *chip); extern bool snd_usb_use_vmalloc; extern bool snd_usb_skip_validation; +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 */