18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *   UAC3 Power Domain state management functions
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/slab.h>
78c2ecf20Sopenharmony_ci#include <linux/usb.h>
88c2ecf20Sopenharmony_ci#include <linux/usb/audio.h>
98c2ecf20Sopenharmony_ci#include <linux/usb/audio-v2.h>
108c2ecf20Sopenharmony_ci#include <linux/usb/audio-v3.h>
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include "usbaudio.h"
138c2ecf20Sopenharmony_ci#include "helper.h"
148c2ecf20Sopenharmony_ci#include "power.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_cistruct snd_usb_power_domain *
178c2ecf20Sopenharmony_cisnd_usb_find_power_domain(struct usb_host_interface *ctrl_iface,
188c2ecf20Sopenharmony_ci			  unsigned char id)
198c2ecf20Sopenharmony_ci{
208c2ecf20Sopenharmony_ci	struct snd_usb_power_domain *pd;
218c2ecf20Sopenharmony_ci	void *p;
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci	pd = kzalloc(sizeof(*pd), GFP_KERNEL);
248c2ecf20Sopenharmony_ci	if (!pd)
258c2ecf20Sopenharmony_ci		return NULL;
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci	p = NULL;
288c2ecf20Sopenharmony_ci	while ((p = snd_usb_find_csint_desc(ctrl_iface->extra,
298c2ecf20Sopenharmony_ci					    ctrl_iface->extralen,
308c2ecf20Sopenharmony_ci					    p, UAC3_POWER_DOMAIN)) != NULL) {
318c2ecf20Sopenharmony_ci		struct uac3_power_domain_descriptor *pd_desc = p;
328c2ecf20Sopenharmony_ci		int i;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci		if (!snd_usb_validate_audio_desc(p, UAC_VERSION_3))
358c2ecf20Sopenharmony_ci			continue;
368c2ecf20Sopenharmony_ci		for (i = 0; i < pd_desc->bNrEntities; i++) {
378c2ecf20Sopenharmony_ci			if (pd_desc->baEntityID[i] == id) {
388c2ecf20Sopenharmony_ci				pd->pd_id = pd_desc->bPowerDomainID;
398c2ecf20Sopenharmony_ci				pd->pd_d1d0_rec =
408c2ecf20Sopenharmony_ci					le16_to_cpu(pd_desc->waRecoveryTime1);
418c2ecf20Sopenharmony_ci				pd->pd_d2d0_rec =
428c2ecf20Sopenharmony_ci					le16_to_cpu(pd_desc->waRecoveryTime2);
438c2ecf20Sopenharmony_ci				return pd;
448c2ecf20Sopenharmony_ci			}
458c2ecf20Sopenharmony_ci		}
468c2ecf20Sopenharmony_ci	}
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	kfree(pd);
498c2ecf20Sopenharmony_ci	return NULL;
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ciint snd_usb_power_domain_set(struct snd_usb_audio *chip,
538c2ecf20Sopenharmony_ci			     struct snd_usb_power_domain *pd,
548c2ecf20Sopenharmony_ci			     unsigned char state)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	struct usb_device *dev = chip->dev;
578c2ecf20Sopenharmony_ci	unsigned char current_state;
588c2ecf20Sopenharmony_ci	int err, idx;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	idx = snd_usb_ctrl_intf(chip) | (pd->pd_id << 8);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0),
638c2ecf20Sopenharmony_ci			      UAC2_CS_CUR,
648c2ecf20Sopenharmony_ci			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
658c2ecf20Sopenharmony_ci			      UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx,
668c2ecf20Sopenharmony_ci			      &current_state, sizeof(current_state));
678c2ecf20Sopenharmony_ci	if (err < 0) {
688c2ecf20Sopenharmony_ci		dev_err(&dev->dev, "Can't get UAC3 power state for id %d\n",
698c2ecf20Sopenharmony_ci			pd->pd_id);
708c2ecf20Sopenharmony_ci		return err;
718c2ecf20Sopenharmony_ci	}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	if (current_state == state) {
748c2ecf20Sopenharmony_ci		dev_dbg(&dev->dev, "UAC3 power domain id %d already in state %d\n",
758c2ecf20Sopenharmony_ci			pd->pd_id, state);
768c2ecf20Sopenharmony_ci		return 0;
778c2ecf20Sopenharmony_ci	}
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR,
808c2ecf20Sopenharmony_ci			      USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
818c2ecf20Sopenharmony_ci			      UAC3_AC_POWER_DOMAIN_CONTROL << 8, idx,
828c2ecf20Sopenharmony_ci			      &state, sizeof(state));
838c2ecf20Sopenharmony_ci	if (err < 0) {
848c2ecf20Sopenharmony_ci		dev_err(&dev->dev, "Can't set UAC3 power state to %d for id %d\n",
858c2ecf20Sopenharmony_ci			state, pd->pd_id);
868c2ecf20Sopenharmony_ci		return err;
878c2ecf20Sopenharmony_ci	}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	if (state == UAC3_PD_STATE_D0) {
908c2ecf20Sopenharmony_ci		switch (current_state) {
918c2ecf20Sopenharmony_ci		case UAC3_PD_STATE_D2:
928c2ecf20Sopenharmony_ci			udelay(pd->pd_d2d0_rec * 50);
938c2ecf20Sopenharmony_ci			break;
948c2ecf20Sopenharmony_ci		case UAC3_PD_STATE_D1:
958c2ecf20Sopenharmony_ci			udelay(pd->pd_d1d0_rec * 50);
968c2ecf20Sopenharmony_ci			break;
978c2ecf20Sopenharmony_ci		default:
988c2ecf20Sopenharmony_ci			return -EINVAL;
998c2ecf20Sopenharmony_ci		}
1008c2ecf20Sopenharmony_ci	}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	dev_dbg(&dev->dev, "UAC3 power domain id %d change to state %d\n",
1038c2ecf20Sopenharmony_ci		pd->pd_id, state);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return 0;
1068c2ecf20Sopenharmony_ci}
107