18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Power management for audio on multifunction CS5535 companion device 48c2ecf20Sopenharmony_ci * Copyright (C) Jaya Kumar 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/init.h> 88c2ecf20Sopenharmony_ci#include <linux/pci.h> 98c2ecf20Sopenharmony_ci#include <linux/delay.h> 108c2ecf20Sopenharmony_ci#include <sound/core.h> 118c2ecf20Sopenharmony_ci#include <sound/control.h> 128c2ecf20Sopenharmony_ci#include <sound/initval.h> 138c2ecf20Sopenharmony_ci#include <sound/asoundef.h> 148c2ecf20Sopenharmony_ci#include <sound/pcm.h> 158c2ecf20Sopenharmony_ci#include <sound/ac97_codec.h> 168c2ecf20Sopenharmony_ci#include "cs5535audio.h" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistatic void snd_cs5535audio_stop_hardware(struct cs5535audio *cs5535au) 198c2ecf20Sopenharmony_ci{ 208c2ecf20Sopenharmony_ci /* 218c2ecf20Sopenharmony_ci we depend on snd_ac97_suspend to tell the 228c2ecf20Sopenharmony_ci AC97 codec to shutdown. the amd spec suggests 238c2ecf20Sopenharmony_ci that the LNK_SHUTDOWN be done at the same time 248c2ecf20Sopenharmony_ci that the codec power-down is issued. instead, 258c2ecf20Sopenharmony_ci we do it just after rather than at the same 268c2ecf20Sopenharmony_ci time. excluding codec specific build_ops->suspend 278c2ecf20Sopenharmony_ci ac97 powerdown hits: 288c2ecf20Sopenharmony_ci 0x8000 EAPD 298c2ecf20Sopenharmony_ci 0x4000 Headphone amplifier 308c2ecf20Sopenharmony_ci 0x0300 ADC & DAC 318c2ecf20Sopenharmony_ci 0x0400 Analog Mixer powerdown (Vref on) 328c2ecf20Sopenharmony_ci I am not sure if this is the best that we can do. 338c2ecf20Sopenharmony_ci The remainder to be investigated are: 348c2ecf20Sopenharmony_ci - analog mixer (vref off) 0x0800 358c2ecf20Sopenharmony_ci - AC-link powerdown 0x1000 368c2ecf20Sopenharmony_ci - codec internal clock 0x2000 378c2ecf20Sopenharmony_ci */ 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci /* set LNK_SHUTDOWN to shutdown AC link */ 408c2ecf20Sopenharmony_ci cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_SHUTDOWN); 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic int __maybe_unused snd_cs5535audio_suspend(struct device *dev) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci struct snd_card *card = dev_get_drvdata(dev); 478c2ecf20Sopenharmony_ci struct cs5535audio *cs5535au = card->private_data; 488c2ecf20Sopenharmony_ci int i; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); 518c2ecf20Sopenharmony_ci snd_ac97_suspend(cs5535au->ac97); 528c2ecf20Sopenharmony_ci for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) { 538c2ecf20Sopenharmony_ci struct cs5535audio_dma *dma = &cs5535au->dmas[i]; 548c2ecf20Sopenharmony_ci if (dma && dma->substream) 558c2ecf20Sopenharmony_ci dma->saved_prd = dma->ops->read_prd(cs5535au); 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci /* save important regs, then disable aclink in hw */ 588c2ecf20Sopenharmony_ci snd_cs5535audio_stop_hardware(cs5535au); 598c2ecf20Sopenharmony_ci return 0; 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic int __maybe_unused snd_cs5535audio_resume(struct device *dev) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci struct snd_card *card = dev_get_drvdata(dev); 658c2ecf20Sopenharmony_ci struct cs5535audio *cs5535au = card->private_data; 668c2ecf20Sopenharmony_ci u32 tmp; 678c2ecf20Sopenharmony_ci int timeout; 688c2ecf20Sopenharmony_ci int i; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci /* set LNK_WRM_RST to reset AC link */ 718c2ecf20Sopenharmony_ci cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_WRM_RST); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci timeout = 50; 748c2ecf20Sopenharmony_ci do { 758c2ecf20Sopenharmony_ci tmp = cs_readl(cs5535au, ACC_CODEC_STATUS); 768c2ecf20Sopenharmony_ci if (tmp & PRM_RDY_STS) 778c2ecf20Sopenharmony_ci break; 788c2ecf20Sopenharmony_ci udelay(1); 798c2ecf20Sopenharmony_ci } while (--timeout); 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci if (!timeout) 828c2ecf20Sopenharmony_ci dev_err(cs5535au->card->dev, "Failure getting AC Link ready\n"); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci /* set up rate regs, dma. actual initiation is done in trig */ 858c2ecf20Sopenharmony_ci for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) { 868c2ecf20Sopenharmony_ci struct cs5535audio_dma *dma = &cs5535au->dmas[i]; 878c2ecf20Sopenharmony_ci if (dma && dma->substream) { 888c2ecf20Sopenharmony_ci dma->substream->ops->prepare(dma->substream); 898c2ecf20Sopenharmony_ci dma->ops->setup_prd(cs5535au, dma->saved_prd); 908c2ecf20Sopenharmony_ci } 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci /* we depend on ac97 to perform the codec power up */ 948c2ecf20Sopenharmony_ci snd_ac97_resume(cs5535au->ac97); 958c2ecf20Sopenharmony_ci snd_power_change_state(card, SNDRV_CTL_POWER_D0); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ciSIMPLE_DEV_PM_OPS(snd_cs5535audio_pm, snd_cs5535audio_suspend, snd_cs5535audio_resume); 101