2023-03-14 01:54:02 +00:00
|
|
|
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
|
|
|
|
// Copyright(c) 2015-2023 Intel Corporation. All rights reserved.
|
|
|
|
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/soundwire/sdw_registers.h>
|
|
|
|
#include <linux/soundwire/sdw.h>
|
|
|
|
#include <linux/soundwire/sdw_intel.h>
|
|
|
|
#include "cadence_master.h"
|
|
|
|
#include "bus.h"
|
|
|
|
#include "intel.h"
|
|
|
|
|
|
|
|
int intel_start_bus(struct sdw_intel *sdw)
|
|
|
|
{
|
|
|
|
struct device *dev = sdw->cdns.dev;
|
|
|
|
struct sdw_cdns *cdns = &sdw->cdns;
|
|
|
|
struct sdw_bus *bus = &cdns->bus;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = sdw_cdns_enable_interrupt(cdns, true);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* follow recommended programming flows to avoid timeouts when
|
|
|
|
* gsync is enabled
|
|
|
|
*/
|
|
|
|
if (bus->multi_link)
|
|
|
|
sdw_intel_sync_arm(sdw);
|
|
|
|
|
|
|
|
ret = sdw_cdns_init(cdns);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "%s: unable to initialize Cadence IP: %d\n", __func__, ret);
|
|
|
|
goto err_interrupt;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sdw_cdns_exit_reset(cdns);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "%s: unable to exit bus reset sequence: %d\n", __func__, ret);
|
|
|
|
goto err_interrupt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bus->multi_link) {
|
|
|
|
ret = sdw_intel_sync_go(sdw);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "%s: sync go failed: %d\n", __func__, ret);
|
|
|
|
goto err_interrupt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sdw_cdns_check_self_clearing_bits(cdns, __func__,
|
|
|
|
true, INTEL_MASTER_RESET_ITERATIONS);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_interrupt:
|
|
|
|
sdw_cdns_enable_interrupt(cdns, false);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int intel_start_bus_after_reset(struct sdw_intel *sdw)
|
|
|
|
{
|
|
|
|
struct device *dev = sdw->cdns.dev;
|
|
|
|
struct sdw_cdns *cdns = &sdw->cdns;
|
|
|
|
struct sdw_bus *bus = &cdns->bus;
|
|
|
|
bool clock_stop0;
|
|
|
|
int status;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* An exception condition occurs for the CLK_STOP_BUS_RESET
|
|
|
|
* case if one or more masters remain active. In this condition,
|
|
|
|
* all the masters are powered on for they are in the same power
|
|
|
|
* domain. Master can preserve its context for clock stop0, so
|
|
|
|
* there is no need to clear slave status and reset bus.
|
|
|
|
*/
|
|
|
|
clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
|
|
|
|
|
|
|
|
if (!clock_stop0) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* make sure all Slaves are tagged as UNATTACHED and
|
|
|
|
* provide reason for reinitialization
|
|
|
|
*/
|
|
|
|
|
|
|
|
status = SDW_UNATTACH_REQUEST_MASTER_RESET;
|
|
|
|
sdw_clear_slave_status(bus, status);
|
|
|
|
|
|
|
|
ret = sdw_cdns_enable_interrupt(cdns, true);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "cannot enable interrupts during resume\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* follow recommended programming flows to avoid
|
|
|
|
* timeouts when gsync is enabled
|
|
|
|
*/
|
|
|
|
if (bus->multi_link)
|
|
|
|
sdw_intel_sync_arm(sdw);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Re-initialize the IP since it was powered-off
|
|
|
|
*/
|
|
|
|
sdw_cdns_init(&sdw->cdns);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
ret = sdw_cdns_enable_interrupt(cdns, true);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "cannot enable interrupts during resume\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sdw_cdns_clock_restart(cdns, !clock_stop0);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "unable to restart clock during resume\n");
|
|
|
|
goto err_interrupt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!clock_stop0) {
|
|
|
|
ret = sdw_cdns_exit_reset(cdns);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "unable to exit bus reset sequence during resume\n");
|
|
|
|
goto err_interrupt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bus->multi_link) {
|
|
|
|
ret = sdw_intel_sync_go(sdw);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(sdw->cdns.dev, "sync go failed during resume\n");
|
|
|
|
goto err_interrupt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_interrupt:
|
|
|
|
sdw_cdns_enable_interrupt(cdns, false);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void intel_check_clock_stop(struct sdw_intel *sdw)
|
|
|
|
{
|
|
|
|
struct device *dev = sdw->cdns.dev;
|
|
|
|
bool clock_stop0;
|
|
|
|
|
|
|
|
clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
|
|
|
|
if (!clock_stop0)
|
|
|
|
dev_err(dev, "%s: invalid configuration, clock was not stopped\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
int intel_start_bus_after_clock_stop(struct sdw_intel *sdw)
|
|
|
|
{
|
|
|
|
struct device *dev = sdw->cdns.dev;
|
|
|
|
struct sdw_cdns *cdns = &sdw->cdns;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = sdw_cdns_enable_interrupt(cdns, true);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sdw_cdns_clock_restart(cdns, false);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "%s: unable to restart clock: %d\n", __func__, ret);
|
|
|
|
sdw_cdns_enable_interrupt(cdns, false);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
sdw_cdns_check_self_clearing_bits(cdns, "intel_resume_runtime no_quirks",
|
|
|
|
true, INTEL_MASTER_RESET_ITERATIONS);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
|
|
|
|
{
|
|
|
|
struct device *dev = sdw->cdns.dev;
|
|
|
|
struct sdw_cdns *cdns = &sdw->cdns;
|
|
|
|
bool wake_enable = false;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (clock_stop) {
|
|
|
|
ret = sdw_cdns_clock_stop(cdns, true);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(dev, "%s: cannot stop clock: %d\n", __func__, ret);
|
|
|
|
else
|
|
|
|
wake_enable = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sdw_cdns_enable_interrupt(cdns, false);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(dev, "%s: cannot disable interrupts: %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sdw_intel_link_power_down(sdw);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "%s: Link power down failed: %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
sdw_intel_shim_wake(sdw, wake_enable);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2023-03-14 01:54:04 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* bank switch routines
|
|
|
|
*/
|
|
|
|
|
|
|
|
int intel_pre_bank_switch(struct sdw_intel *sdw)
|
|
|
|
{
|
|
|
|
struct sdw_cdns *cdns = &sdw->cdns;
|
|
|
|
struct sdw_bus *bus = &cdns->bus;
|
|
|
|
|
|
|
|
/* Write to register only for multi-link */
|
|
|
|
if (!bus->multi_link)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
sdw_intel_sync_arm(sdw);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int intel_post_bank_switch(struct sdw_intel *sdw)
|
|
|
|
{
|
|
|
|
struct sdw_cdns *cdns = &sdw->cdns;
|
|
|
|
struct sdw_bus *bus = &cdns->bus;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
/* Write to register only for multi-link */
|
|
|
|
if (!bus->multi_link)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
mutex_lock(sdw->link_res->shim_lock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* post_bank_switch() ops is called from the bus in loop for
|
|
|
|
* all the Masters in the steam with the expectation that
|
|
|
|
* we trigger the bankswitch for the only first Master in the list
|
|
|
|
* and do nothing for the other Masters
|
|
|
|
*
|
|
|
|
* So, set the SYNCGO bit only if CMDSYNC bit is set for any Master.
|
|
|
|
*/
|
|
|
|
if (sdw_intel_sync_check_cmdsync_unlocked(sdw))
|
|
|
|
ret = sdw_intel_sync_go_unlocked(sdw);
|
|
|
|
|
|
|
|
mutex_unlock(sdw->link_res->shim_lock);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
dev_err(sdw->cdns.dev, "Post bank switch failed: %d\n", ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|