// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
* SoundWire AMD Manager driver
*
* Copyright 2023-24 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_init.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 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)
{
u32 val;
mutex_lock(amd_manager->acp_sdw_lock);
val = sdw_manager_reg_mask_array[amd_manager->instance];
amd_updatel(amd_manager->acp_mmio, ACP_EXTERNAL_INTR_CNTL(amd_manager->instance), val, val);
mutex_unlock(amd_manager->acp_sdw_lock);
writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
}
static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager)
{
u32 irq_mask;
mutex_lock(amd_manager->acp_sdw_lock);
irq_mask = sdw_manager_reg_mask_array[amd_manager->instance];
amd_updatel(amd_manager->acp_mmio, ACP_EXTERNAL_INTR_CNTL(amd_manager->instance),
irq_mask, 0);
mutex_unlock(amd_manager->acp_sdw_lock);
writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);