// SPDX-License-Identifier: GPL-2.0+
/*
* SoundWire AMD Manager driver
*
* Copyright 2023 Advanced Micro Devices, Inc.
*/
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/pm_runtime.h>
#include <linux/wait.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "bus.h"
#include "amd_manager.h"
#define DRV_NAME "amd_sdw_manager"
#define to_amd_sdw(b) container_of(b, struct amd_sdw_manager, bus)
static void amd_enable_sdw_pads(struct amd_sdw_manager *amd_manager)
{
u32 sw_pad_pulldown_val;
u32 val;
mutex_lock(amd_manager->acp_sdw_lock);
val = readl(amd_manager->acp_mmio + ACP_SW_PAD_KEEPER_EN);
val |= amd_manager->reg_mask->sw_pad_enable_mask;
writel(val, amd_manager->acp_mmio + ACP_SW_PAD_KEEPER_EN);
usleep_range(1000, 1500);
sw_pad_pulldown_val = readl(amd_manager->acp_mmio + ACP_PAD_PULLDOWN_CTRL);
sw_pad_pulldown_val &= amd_manager->reg_mask->sw_pad_pulldown_mask;
writel(sw_pad_pulldown_val, amd_manager->acp_mmio + ACP_PAD_PULLDOWN_CTRL);
mutex_unlock(amd_manager->acp_sdw_lock);
}
static int amd_init_sdw_manager(struct amd_sdw_manager *amd_manager)
{
u32 val;
int ret;
writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_EN_STATUS, val, val, ACP_DELAY_US,
AMD_SDW_TIMEOUT);
if (ret)
return ret;
/* SoundWire manager bus reset */
writel(AMD_SDW_BUS_RESET_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_BUS_RESET_CTRL, val,
(val & AMD_SDW_BUS_RESET_DONE), ACP_DELAY_US, AMD_SDW_TIMEOUT);
if (ret)
return ret;
writel(AMD_SDW_BUS_RESET_CLEAR_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_BUS_RESET_CTRL, val, !val,
ACP_DELAY_US, AMD_SDW_TIMEOUT);
if (ret) {
dev_err(amd_manager->dev, "Failed to reset SoundWire manager instance%d\n",
amd_manager->instance);
return ret;
}
writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
return readl_poll_timeout(amd_manager->mmio + ACP_SW_EN_STATUS, val, !val, ACP_DELAY_US,
AMD_SDW_TIMEOUT);
}
static int amd_enable_sdw_manager(struct amd_sdw_manager *amd_manager)
{
u32 val;
writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
return readl_poll_timeout(amd_manager->mmio + ACP_SW_EN_STATUS, val, val, ACP_DELAY_US,
AMD_SDW_TIMEOUT);
}
static int amd_disable_sdw_manager(struct amd_sdw_manager *amd_manager)
{
u32 val;
writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
/*
* After invoking manager disable sequence, check whether
* manager has executed clock stop sequence. In this case,
* manager should ignore checking enable status register.
*/
val = readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
if (val)
return 0;
return readl_poll_timeout(amd_manager->mmio + ACP_SW_EN_STATUS, val, !val, ACP_DELAY_US,
AMD_SDW_TIMEOUT);
}
static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager)
{
struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
u32 val;
mutex_lock(amd_manager->acp_sdw_lock);
val = readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
val |= reg_mask->acp_sdw_intr_mask;
writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
mutex_unlock