blob: 5d03aba418d032dc18d116fa9005c62eb4ca2c45 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Google Corp.
*
* Author:
* Puma Hsu <[email protected]>
*/
#include <linux/usb.h>
#include <trace/hooks/sound.h>
#include <trace/hooks/usb.h>
#include "aoc_usb.h"
/*
* 1: enable AP suspend support
* 0: disable AP suspend support
*/
#define ap_suspend_enabled 1
/*
* Return true when the platform supports AP suspend with USB awake.
* Currently this will respond to Kernel alsa pcm driver.
*/
static void sound_vendor_support_suspend(void *unused, struct usb_device *udev,
int direction, bool *is_support)
{
if (!udev)
return;
*is_support = true;
return;
}
/*
* This returns the enum of device/hub info.
* Currently we only distinguish one layer usb device, so it only
* has usb1~3, and usb1-1/usb2-1/usb3-1. For the other cases we
* return UNDEFINED.
* TODO(b/199883028): optimize this function to be flexible.
*/
static int get_usb_dev_hub_info(struct usb_device *udev)
{
enum usb_dev_hub usb_dev_hub = UNDEFINED;
if (!udev)
return usb_dev_hub;
if (!udev->parent) {
/* hub */
switch (udev->bus->busnum) {
case 1:
usb_dev_hub = USB1;
break;
case 2:
usb_dev_hub = USB2;
break;
case 3:
usb_dev_hub = USB3;
break;
default:
usb_dev_hub = UNDEFINED;
break;
}
} else {
/* device */
switch (udev->bus->busnum) {
case 1:
if (udev->portnum == 1)
usb_dev_hub = USB1_1;
break;
case 2:
if (udev->portnum == 1)
usb_dev_hub = USB2_1;
break;
case 3:
if (udev->portnum == 1)
usb_dev_hub = USB3_1;
break;
default:
usb_dev_hub = UNDEFINED;
break;
}
}
return usb_dev_hub;
}
/*
* Set bypass = 1 will skip USB suspend.
*/
static void usb_vendor_dev_suspend(void *unused, struct usb_device *udev,
pm_message_t msg, int *bypass)
{
struct xhci_hcd *xhci;
int usb_audio_count = 0;
bool usb_playback = false;
bool usb_capture = false;
enum usb_dev_hub usb_dev_hub;
*bypass = 0;
if (!udev)
return;
usb_dev_hub = get_usb_dev_hub_info(udev);
/*
* We don't change dummy_hcd's behavior, the dummy_hcd is in bus 1.
* We don't change undefined device neither.
*/
if (usb_dev_hub >= USB1 && usb_dev_hub <= USB1_1)
return;
xhci = get_xhci_hcd_by_udev(udev);
if (!xhci) {
dev_err(&udev->dev, "%s: couldn't get xhci\n", __func__);
return;
}
usb_audio_count = xhci_get_usb_audio_count(xhci);
if (usb_audio_count > 0) {
/*
* We query playback/capture states only when there is USB
* audio device connected.
*/
usb_playback = aoc_alsa_usb_playback_enabled();
usb_capture = aoc_alsa_usb_capture_enabled();
} else {
/* If no USB audio device is connected, we won't skip suspend. */
return;
}
/*
* Note: Currently we also allow the UNDEFINED case go to check
* playback/capture state. For example, the headset behind hub
* may be USB2-1.1
*/
if ((usb_dev_hub >= USB2 && usb_dev_hub <= USB3_1) || usb_dev_hub == UNDEFINED) {
if (usb_playback || usb_capture) {
dev_info(&udev->dev, "%s: skip suspend process (playback:%d,capture:%d)\n",
__func__, usb_playback, usb_capture);
*bypass = 1;
}
}
return;
}
/*
* Set bypass = 1 will skip USB resume.
*/
static void usb_vendor_dev_resume(void *unused, struct usb_device *udev,
pm_message_t msg, int *bypass)
{
enum usb_dev_hub usb_dev_hub;
if (!udev) {
*bypass = 0;
return;
}
usb_dev_hub = get_usb_dev_hub_info(udev);
/*
* We don't change dummy_hcd's behavior, the dummy_hcd is in bus 1.
* We don't change undefined device neither.
*/
if (usb_dev_hub <= USB1_1) {
*bypass = 0;
return;
}
if (udev->port_is_suspended || udev->state == USB_STATE_SUSPENDED) {
*bypass = 0;
} else {
dev_info(&udev->dev, "%s: skip resume process\n", __func__);
*bypass = 1;
}
return;
}
int usb_vendor_helper_init(void)
{
int ret = 0;
if (ap_suspend_enabled) {
pr_info("%s: AP suspend support is enabled\n", __func__);
} else {
pr_info("%s: AP suspend support is disabled\n", __func__);
return ret;
}
ret = register_trace_android_vh_sound_usb_support_cpu_suspend(sound_vendor_support_suspend,
NULL);
if (ret)
pr_err("register_trace_android_vh_sound_usb_support_cpu_suspend failed, ret:%d\n",
ret);
ret = register_trace_android_vh_usb_dev_suspend(usb_vendor_dev_suspend, NULL);
if (ret)
pr_err("register_trace_android_vh_usb_dev_suspend failed, ret:%d\n", ret);
ret = register_trace_android_vh_usb_dev_resume(usb_vendor_dev_resume, NULL);
if (ret)
pr_err("register_trace_android_vh_usb_dev_resume failed, ret:%d\n", ret);
return ret;
}