162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *   UAC3 Power Domain state management functions
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/slab.h>
762306a36Sopenharmony_ci#include <linux/usb.h>
862306a36Sopenharmony_ci#include <linux/usb/audio.h>
962306a36Sopenharmony_ci#include <linux/usb/audio-v2.h>
1062306a36Sopenharmony_ci#include <linux/usb/audio-v3.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "usbaudio.h"
1362306a36Sopenharmony_ci#include "helper.h"
1462306a36Sopenharmony_ci#include "power.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistruct snd_usb_power_domain *
1762306a36Sopenharmony_cisnd_usb_find_power_domain(struct usb_host_interface *ctrl_iface,
1862306a36Sopenharmony_ci			  unsigned char id)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	struct snd_usb_power_domain *pd;
2162306a36Sopenharmony_ci	void *p;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	pd = kzalloc(sizeof(*pd), GFP_KERNEL);
2462306a36Sopenharmony_ci	if (!pd)
2562306a36Sopenharmony_ci		return NULL;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	p = NULL;
2862306a36Sopenharmony_ci	while ((p = snd_usb_find_csint_desc(ctrl_iface->extra,
2962306a36Sopenharmony_ci					    ctrl_iface->extralen,
3062306a36Sopenharmony_ci					    p, UAC3_POWER_DOMAIN)) != NULL) {
3162306a36Sopenharmony_ci		struct uac3_power_domain_descriptor *pd_desc = p;
3262306a36Sopenharmony_ci		int i;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci		if (!snd_usb_validate_audio_desc(p, UAC_VERSION_3))
3562306a36Sopenharmony_ci			continue;
3662306a36Sopenharmony_ci		for (i = 0; i < pd_desc->bNrEntities; i++) {
3762306a36Sopenharmony_ci			if (pd_desc->baEntityID[i] == id) {
3862306a36Sopenharmony_ci				pd->pd_id = pd_desc->bPowerDomainID;
3962306a36Sopenharmony_ci				pd->pd_d1d0_rec =
4062306a36Sopenharmony_ci					le16_to_cpu(pd_desc->waRecoveryTime1);
4162306a36Sopenharmony_ci				pd->pd_d2d0_rec =
4262306a36Sopenharmony_ci					le16_to_cpu(pd_desc->waRecoveryTime2);
4362306a36Sopenharmony_ci				return pd;
4462306a36Sopenharmony_ci			}
4562306a36Sopenharmony_ci		}
4662306a36Sopenharmony_ci	}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	kfree(pd);
4962306a36Sopenharmony_ci	return NULL;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ciint snd_usb_power_domain_set(struct snd_usb_audio *chip,
5362306a36Sopenharmony_ci			     struct snd_usb_power_domain *pd,
5462306a36Sopenharmony_ci			     unsigned char state)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	struct usb_device *dev = chip->dev;
5762306a36Sopenharmony_ci	unsigned char current_state;
5862306a36Sopenharmony_ci	int err, idx;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	idx = snd_usb_ctrl_intf(chip) | (pd->pd_id << 8);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
6362306a36Sopenharmony_ci			      UAC2_CS_CUR,
6462306a36Sopenharmony_ci			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
6562306a36Sopenharmony_ci			      UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx,
6662306a36Sopenharmony_ci			      &current_state, sizeof(current_state));
6762306a36Sopenharmony_ci	if (err < 0) {
6862306a36Sopenharmony_ci		dev_err(&dev->dev, "Can't get UAC3 power state for id %d\n",
6962306a36Sopenharmony_ci			pd->pd_id);
7062306a36Sopenharmony_ci		return err;
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (current_state == state) {
7462306a36Sopenharmony_ci		dev_dbg(&dev->dev, "UAC3 power domain id %d already in state %d\n",
7562306a36Sopenharmony_ci			pd->pd_id, state);
7662306a36Sopenharmony_ci		return 0;
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR,
8062306a36Sopenharmony_ci			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
8162306a36Sopenharmony_ci			      UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx,
8262306a36Sopenharmony_ci			      &state, sizeof(state));
8362306a36Sopenharmony_ci	if (err < 0) {
8462306a36Sopenharmony_ci		dev_err(&dev->dev, "Can't set UAC3 power state to %d for id %d\n",
8562306a36Sopenharmony_ci			state, pd->pd_id);
8662306a36Sopenharmony_ci		return err;
8762306a36Sopenharmony_ci	}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (state == UAC3_PD_STATE_D0) {
9062306a36Sopenharmony_ci		switch (current_state) {
9162306a36Sopenharmony_ci		case UAC3_PD_STATE_D2:
9262306a36Sopenharmony_ci			udelay(pd->pd_d2d0_rec * 50);
9362306a36Sopenharmony_ci			break;
9462306a36Sopenharmony_ci		case UAC3_PD_STATE_D1:
9562306a36Sopenharmony_ci			udelay(pd->pd_d1d0_rec * 50);
9662306a36Sopenharmony_ci			break;
9762306a36Sopenharmony_ci		default:
9862306a36Sopenharmony_ci			return -EINVAL;
9962306a36Sopenharmony_ci		}
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	dev_dbg(&dev->dev, "UAC3 power domain id %d change to state %d\n",
10362306a36Sopenharmony_ci		pd->pd_id, state);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return 0;
10662306a36Sopenharmony_ci}
107