162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Power management for audio on multifunction CS5535 companion device 462306a36Sopenharmony_ci * Copyright (C) Jaya Kumar 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/init.h> 862306a36Sopenharmony_ci#include <linux/pci.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <sound/core.h> 1162306a36Sopenharmony_ci#include <sound/control.h> 1262306a36Sopenharmony_ci#include <sound/initval.h> 1362306a36Sopenharmony_ci#include <sound/asoundef.h> 1462306a36Sopenharmony_ci#include <sound/pcm.h> 1562306a36Sopenharmony_ci#include <sound/ac97_codec.h> 1662306a36Sopenharmony_ci#include "cs5535audio.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic void snd_cs5535audio_stop_hardware(struct cs5535audio *cs5535au) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci /* 2162306a36Sopenharmony_ci we depend on snd_ac97_suspend to tell the 2262306a36Sopenharmony_ci AC97 codec to shutdown. the amd spec suggests 2362306a36Sopenharmony_ci that the LNK_SHUTDOWN be done at the same time 2462306a36Sopenharmony_ci that the codec power-down is issued. instead, 2562306a36Sopenharmony_ci we do it just after rather than at the same 2662306a36Sopenharmony_ci time. excluding codec specific build_ops->suspend 2762306a36Sopenharmony_ci ac97 powerdown hits: 2862306a36Sopenharmony_ci 0x8000 EAPD 2962306a36Sopenharmony_ci 0x4000 Headphone amplifier 3062306a36Sopenharmony_ci 0x0300 ADC & DAC 3162306a36Sopenharmony_ci 0x0400 Analog Mixer powerdown (Vref on) 3262306a36Sopenharmony_ci I am not sure if this is the best that we can do. 3362306a36Sopenharmony_ci The remainder to be investigated are: 3462306a36Sopenharmony_ci - analog mixer (vref off) 0x0800 3562306a36Sopenharmony_ci - AC-link powerdown 0x1000 3662306a36Sopenharmony_ci - codec internal clock 0x2000 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci /* set LNK_SHUTDOWN to shutdown AC link */ 4062306a36Sopenharmony_ci cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_SHUTDOWN); 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic int __maybe_unused snd_cs5535audio_suspend(struct device *dev) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci struct snd_card *card = dev_get_drvdata(dev); 4762306a36Sopenharmony_ci struct cs5535audio *cs5535au = card->private_data; 4862306a36Sopenharmony_ci int i; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); 5162306a36Sopenharmony_ci snd_ac97_suspend(cs5535au->ac97); 5262306a36Sopenharmony_ci for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) { 5362306a36Sopenharmony_ci struct cs5535audio_dma *dma = &cs5535au->dmas[i]; 5462306a36Sopenharmony_ci if (dma && dma->substream) 5562306a36Sopenharmony_ci dma->saved_prd = dma->ops->read_prd(cs5535au); 5662306a36Sopenharmony_ci } 5762306a36Sopenharmony_ci /* save important regs, then disable aclink in hw */ 5862306a36Sopenharmony_ci snd_cs5535audio_stop_hardware(cs5535au); 5962306a36Sopenharmony_ci return 0; 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic int __maybe_unused snd_cs5535audio_resume(struct device *dev) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci struct snd_card *card = dev_get_drvdata(dev); 6562306a36Sopenharmony_ci struct cs5535audio *cs5535au = card->private_data; 6662306a36Sopenharmony_ci u32 tmp; 6762306a36Sopenharmony_ci int timeout; 6862306a36Sopenharmony_ci int i; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* set LNK_WRM_RST to reset AC link */ 7162306a36Sopenharmony_ci cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_WRM_RST); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci timeout = 50; 7462306a36Sopenharmony_ci do { 7562306a36Sopenharmony_ci tmp = cs_readl(cs5535au, ACC_CODEC_STATUS); 7662306a36Sopenharmony_ci if (tmp & PRM_RDY_STS) 7762306a36Sopenharmony_ci break; 7862306a36Sopenharmony_ci udelay(1); 7962306a36Sopenharmony_ci } while (--timeout); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci if (!timeout) 8262306a36Sopenharmony_ci dev_err(cs5535au->card->dev, "Failure getting AC Link ready\n"); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci /* set up rate regs, dma. actual initiation is done in trig */ 8562306a36Sopenharmony_ci for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) { 8662306a36Sopenharmony_ci struct cs5535audio_dma *dma = &cs5535au->dmas[i]; 8762306a36Sopenharmony_ci if (dma && dma->substream) { 8862306a36Sopenharmony_ci dma->substream->ops->prepare(dma->substream); 8962306a36Sopenharmony_ci dma->ops->setup_prd(cs5535au, dma->saved_prd); 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci /* we depend on ac97 to perform the codec power up */ 9462306a36Sopenharmony_ci snd_ac97_resume(cs5535au->ac97); 9562306a36Sopenharmony_ci snd_power_change_state(card, SNDRV_CTL_POWER_D0); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return 0; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ciSIMPLE_DEV_PM_OPS(snd_cs5535audio_pm, snd_cs5535audio_suspend, snd_cs5535audio_resume); 101