// SPDX-License-Identifier: GPL-2.0-only
//
// HDA audio driver for Cirrus Logic CS35L56 smart amp
//
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
//
#include <linux/acpi.h>
#include <linux/debugfs.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/cs-amp-lib.h>
#include <sound/hda_codec.h>
#include <sound/tlv.h>
#include "cirrus_scodec.h"
#include "cs35l56_hda.h"
#include "hda_component.h"
#include "hda_cs_dsp_ctl.h"
#include "hda_generic.h"
/*
* The cs35l56_hda_dai_config[] reg sequence configures the device as
* ASP1_BCLK_FREQ = 3.072 MHz
* ASP1_RX_WIDTH = 32 cycles per slot, ASP1_TX_WIDTH = 32 cycles per slot, ASP1_FMT = I2S
* ASP1_DOUT_HIZ_CONTROL = Hi-Z during unused timeslots
* ASP1_RX_WL = 24 bits per sample
* ASP1_TX_WL = 24 bits per sample
* ASP1_RXn_EN 1..3 and ASP1_TXn_EN 1..4 disabled
*
* Override any Windows-specific mixer settings applied by the firmware.
*/
static const struct reg_sequence cs35l56_hda_dai_config[] = {
{ CS35L56_ASP1_CONTROL1, 0x00000021 },
{ CS35L56_ASP1_CONTROL2, 0x20200200 },
{ CS35L56_ASP1_CONTROL3, 0x00000003 },
{ CS35L56_ASP1_FRAME_CONTROL1, 0x03020100 },
{ CS35L56_ASP1_FRAME_CONTROL5, 0x00020100 },
{ CS35L56_ASP1_DATA_CONTROL5, 0x00000018 },
{ CS35L56_ASP1_DATA_CONTROL1, 0x00000018 },
{ CS35L56_ASP1_ENABLES1, 0x00000000 },
{ CS35L56_ASP1TX1_INPUT, 0x00000018 },
{ CS35L56_ASP1TX2_INPUT, 0x00000019 },
{ CS35L56_ASP1TX3_INPUT, 0x00000020 },
{ CS35L56_ASP1TX4_INPUT, 0x00000028 },
};
static void cs35l56_hda_wait_dsp_ready(struct cs35l56_hda *cs35l56)
{
/* Wait for patching to complete */
flush_work(&cs35l56->dsp_work);
}
static void cs35l56_hda_play(struct cs35l56_hda *cs35l56)
{
unsigned int val;
int ret;
cs35l56_hda_wait_dsp_ready(cs35l56);
pm_runtime_get_sync(cs35l56->base.dev);
ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PLAY);
if (ret == 0) {
/* Wait for firmware to enter PS0 power state */
ret = regmap_read_poll_timeout(cs35l56->base.regmap,
CS35L56_TRANSDUCER_ACTUAL_PS,
val, (val == CS35L56_PS0),
CS35L56_PS0_POLL_US,
CS35L56_PS0_TIMEOUT_US);
if (ret)
dev_warn(cs35l56->base.dev, "PS0 wait failed: %d\n", ret);
}
regmap_set_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1,
BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) |
cs35l56->asp_tx_mask);
cs35l56->playing = true;
}
static void cs35l56_hda_pause(struct cs35l56_hda *cs35l56)
{
cs35l56->playing = false;
cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PAUSE);
regmap_clear_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1,
BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) |
BIT(CS35L56_ASP_TX1_EN_SHIFT) | BIT(CS35L56_ASP_TX2_EN_SHIFT) |
BIT(CS35L56_ASP_TX3_EN_SHIFT) | BIT(CS35L56_ASP_TX4_EN_SHIFT));
pm_runtime_mark_last_busy(cs35l56->base.dev);
pm_runtime_put_autosuspend(cs35l56->base.dev);
}
static void cs35l56_hda_playback_hook(struct device *dev, int action)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
dev_dbg(cs35l56->base.dev, "%s()%d: action: %d\n", __func__, __LINE__, action);
switch (action) {
case HDA_GEN_PCM_ACT_PREPARE:
if (cs35l56->playing)
break;
/* If we're suspended: flag that resume should start playback */
if (cs35l56->suspended) {
cs35l56->playing = true;
break;
}
cs35l56_hda_play(cs35l56);
break;
case HDA_GEN_PCM_ACT_CLEANUP:
if (!cs35l56->playing)
break;
cs35l56_hda_pause(cs35l56);
break;
default:
break;
}
}
static int cs35l56_hda_runtime_suspend(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
if (cs35l56->cs_dsp.booted)
cs_dsp_stop(&cs35l56->cs_dsp);
return cs35l56_runtime_suspend_common(&cs35l56->base);
}
static int cs35l56_hda_runtime_resume(struct device *dev)
{
struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev);
int ret;
ret = cs35l56_runtime_resume_common(&cs35l56->base, false);
if (ret < 0)
return ret;
if (cs35l56->cs_dsp.booted) {
ret = cs_dsp_run(&cs35l56->cs_dsp);
if (ret) {
dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__,