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 ¤t_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