[PATCH 1/2] 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.
Signed-off-by: JaeHun Jung jh0801.jung@samsung.com Signed-off-by: Oh Eomji eomji.oh@samsung.com --- sound/usb/card.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/card.h | 21 +++++++++ sound/usb/usbaudio.h | 46 ++++++++++++++++++++ 3 files changed, 187 insertions(+)
diff --git a/sound/usb/card.c b/sound/usb/card.c index 3769622..ac81c5f 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -117,6 +117,118 @@ MODULE_PARM_DESC(skip_validation, "Skip unit descriptor validation (default: no) 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(const struct audioformat *fp, + const 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; +}
/* * disconnect streams @@ -753,6 +865,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 */ @@ -820,6 +936,8 @@ static int usb_audio_probe(struct usb_interface *intf, if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) usb_disable_autosuspend(interface_to_usbdev(intf));
+ 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 @@ -907,6 +1025,8 @@ static void usb_audio_disconnect(struct usb_interface *intf)
card = chip->card;
+ 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 87f042d..2b686e4 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -204,4 +204,25 @@ struct snd_usb_stream { struct list_head list; };
+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(const struct audioformat *fp, + const struct audioformat *found, + int *cur_attr, int *attr); + #endif /* __USBAUDIO_CARD_H */ diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 1678341..edcb5a3 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -184,4 +184,50 @@ extern bool snd_usb_skip_validation; #define QUIRK_FLAG_DSD_RAW (1U << 15) #define QUIRK_FLAG_SET_IFACE_FIRST (1U << 16)
+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)(const struct audioformat *fp, + const struct audioformat *found, + int *cur_attr, int *attr); + int (*usb_add_ctls)(struct snd_usb_audio *chip); +}; + #endif /* __USBAUDIO_H */
When a new interface is connected or removed, the call-back functions are called to transmit a command to the hardware.
Signed-off-by: Oh Eomji eomji.oh@samsung.com --- sound/usb/pcm.c | 40 ++++++++++++++++++++++++++++++++++++++++ sound/usb/stream.c | 2 ++ 2 files changed, 42 insertions(+)
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index cec6e91a..92e5e82 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -144,6 +144,8 @@ find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, found = fp; cur_attr = attr; } + + snd_vendor_set_pcm_binterval(fp, found, &cur_attr, &attr); } return found; } @@ -434,6 +436,7 @@ static int configure_endpoints(struct snd_usb_audio *chip, struct snd_usb_substream *subs) { int err; + struct usb_interface *iface;
if (subs->data_endpoint->need_setup) { /* stop any running stream beforehand */ @@ -442,6 +445,13 @@ static int configure_endpoints(struct snd_usb_audio *chip, err = snd_usb_endpoint_configure(chip, subs->data_endpoint); if (err < 0) return err; + + iface = usb_ifnum_to_if(chip->dev, subs->data_endpoint->iface); + err = snd_vendor_set_pcm_intf(iface, subs->data_endpoint->iface, + subs->data_endpoint->altsetting, subs->direction); + if (err < 0) + return err; + snd_usb_set_format_quirk(subs, subs->cur_audiofmt); }
@@ -616,8 +626,19 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct snd_usb_substream *subs = runtime->private_data; struct snd_usb_audio *chip = subs->stream->chip; + struct snd_usb_endpoint *ep = subs->data_endpoint; + 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; + } + ret = snd_usb_lock_shutdown(chip); if (ret < 0) return ret; @@ -630,6 +651,15 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream) if (ret < 0) goto unlock;
+ iface = usb_ifnum_to_if(chip->dev, ep->cur_audiofmt->iface); + + if (snd_vendor_get_ops()) { + ret = snd_vendor_set_rate(iface, ep->cur_audiofmt->iface, + ep->cur_rate, ep->cur_audiofmt->altsetting); + if (!ret) + goto unlock; + } + /* reset the pointer */ subs->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size); subs->inflight_bytes = 0; @@ -1104,6 +1134,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; + runtime->hw = snd_usb_hardware; /* need an explicit sync to catch applptr update in low-latency mode */ if (direction == SNDRV_PCM_STREAM_PLAYBACK && @@ -1137,6 +1172,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; + snd_media_stop_pipeline(subs);
if (!snd_usb_lock_shutdown(subs->stream->chip)) { diff --git a/sound/usb/stream.c b/sound/usb/stream.c index ceb93d7..26ca696 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -1227,6 +1227,8 @@ static int __snd_usb_parse_audio_interface(struct snd_usb_audio *chip, snd_usb_init_pitch(chip, fp); snd_usb_init_sample_rate(chip, fp, fp->rate_max); usb_set_interface(chip->dev, iface_no, altno); + if (protocol > UAC_VERSION_1) + snd_vendor_set_interface(chip->dev, alts, iface_no, 0); } return 0; }
On Mon, Mar 07, 2022 at 11:21:59AM +0900, Oh Eomji wrote:
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
You need to supply the users of the callbacks/hooks, otherwise we can not properly evaluate them, nor can we accept them.
I have been saying this for over a year, please submit ALL of the needed parts for these hooks to even be able to be considered. Otherwise they will just be ignored for the obvious reasons stated numerous times in the past.
thanks,
greg k-h
On Mon, Mar 07, 2022 at 11:21:59AM +0900, Oh Eomji wrote:
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.
Also, this is a non-starter, and not going to work at all, sorry. Most devices have more than one USB controller in the system, and we can never accept changes that break that model.
Nor would you want us to, as it would break many Samsung devices...
thanks,
greg k-h
participants (2)
-
Greg Kroah-Hartman
-
Oh Eomji