Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6b46ac21 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "ASoC: wcd-spi: add system suspend and resume functionality"

parents f445080d 91b8de6e
Loading
Loading
Loading
Loading
+41 −4
Original line number Diff line number Diff line
/*
 * Copyright (c) 2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -881,12 +880,50 @@ done:

static int wdsp_suspend(struct device *wdsp_dev)
{
	return 0;
	struct wdsp_mgr_priv *wdsp;
	int rc = 0, i;

	if (!wdsp_dev) {
		pr_err("%s: Invalid handle to device\n", __func__);
		return -EINVAL;
	}

	wdsp = dev_get_drvdata(wdsp_dev);

	for (i =  WDSP_CMPNT_TYPE_MAX - 1; i >= 0; i--) {
		rc = wdsp_unicast_event(wdsp, i, WDSP_EVENT_SUSPEND, NULL);
		if (rc < 0) {
			WDSP_ERR(wdsp, "component %s failed to suspend\n",
				WDSP_GET_CMPNT_TYPE_STR(i));
			break;
		}
	}

	return rc;
}

static int wdsp_resume(struct device *wdsp_dev)
{
	return 0;
	struct wdsp_mgr_priv *wdsp;
	int rc = 0, i;

	if (!wdsp_dev) {
		pr_err("%s: Invalid handle to device\n", __func__);
		return -EINVAL;
	}

	wdsp = dev_get_drvdata(wdsp_dev);

	for (i =  0; i < WDSP_CMPNT_TYPE_MAX; i++) {
		rc = wdsp_unicast_event(wdsp, i, WDSP_EVENT_RESUME, NULL);
		if (rc < 0) {
			WDSP_ERR(wdsp, "component %s failed to resume\n",
				WDSP_GET_CMPNT_TYPE_STR(i));
			break;
		}
	}

	return rc;
}

static struct wdsp_mgr_ops wdsp_ops = {
+138 −3
Original line number Diff line number Diff line
/*
 * Copyright (c) 2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -60,7 +59,8 @@

/* Command delays */
#define WCD_SPI_CLKREQ_DELAY_USECS (500)
#define WCD_SPI_CLK_OFF_TIMER_MS   (3000)
#define WCD_SPI_CLK_OFF_TIMER_MS   (500)
#define WCD_SPI_RESUME_TIMEOUT_MS 100

/* Command masks */
#define WCD_CMD_ADDR_MASK            \
@@ -90,6 +90,7 @@

/* Status mask bits */
#define WCD_SPI_CLK_STATE_ENABLED BIT(0)
#define WCD_SPI_IS_SUSPENDED BIT(1)

/* Locking related */
#define WCD_SPI_MUTEX_LOCK(spi, lock)              \
@@ -144,6 +145,9 @@ struct wcd_spi_priv {

	/* Debugfs related information */
	struct wcd_spi_debug_data debug_data;

	/* Completion object to indicate system resume completion */
	struct completion resume_comp;
};

enum xfer_request {
@@ -170,6 +174,55 @@ static void wcd_spi_reinit_xfer(struct spi_transfer *xfer)
	xfer->len = 0;
}

static bool wcd_spi_is_suspended(struct wcd_spi_priv *wcd_spi)
{
	return test_bit(WCD_SPI_IS_SUSPENDED, &wcd_spi->status_mask);
}

static bool wcd_spi_can_suspend(struct wcd_spi_priv *wcd_spi)
{
	struct spi_device *spi = wcd_spi->spi;

	if (wcd_spi->clk_users > 0 ||
	    test_bit(WCD_SPI_CLK_STATE_ENABLED, &wcd_spi->status_mask)) {
		dev_err(&spi->dev, "%s: cannot suspend, clk_users = %d\n",
			__func__, wcd_spi->clk_users);
		return false;
	}

	return true;
}

static int wcd_spi_wait_for_resume(struct wcd_spi_priv *wcd_spi)
{
	struct spi_device *spi = wcd_spi->spi;
	int rc = 0;

	WCD_SPI_MUTEX_LOCK(spi, wcd_spi->clk_mutex);
	/* If the system is already in resumed state, return right away */
	if (!wcd_spi_is_suspended(wcd_spi))
		goto done;

	/* If suspended then wait for resume to happen */
	reinit_completion(&wcd_spi->resume_comp);
	WCD_SPI_MUTEX_UNLOCK(spi, wcd_spi->clk_mutex);
	rc = wait_for_completion_timeout(&wcd_spi->resume_comp,
				msecs_to_jiffies(WCD_SPI_RESUME_TIMEOUT_MS));
	WCD_SPI_MUTEX_LOCK(spi, wcd_spi->clk_mutex);
	if (rc == 0) {
		dev_err(&spi->dev, "%s: failed to resume in %u msec\n",
			__func__, WCD_SPI_RESUME_TIMEOUT_MS);
		rc = -EIO;
		goto done;
	}

	dev_dbg(&spi->dev, "%s: resume successful\n", __func__);
	rc = 0;
done:
	WCD_SPI_MUTEX_UNLOCK(spi, wcd_spi->clk_mutex);
	return rc;
}

static int wcd_spi_read_single(struct spi_device *spi,
			       u32 remote_addr, u32 *val)
{
@@ -579,6 +632,18 @@ static int wcd_spi_clk_ctrl(struct spi_device *spi,
	}

	if (request == WCD_SPI_CLK_ENABLE) {
		/*
		 * If the SPI bus is suspended, then return error
		 * as the transaction cannot be completed.
		 */
		if (wcd_spi_is_suspended(wcd_spi)) {
			dev_err(&spi->dev,
				"%s: SPI suspended, cannot enable clk\n",
				__func__);
			ret = -EIO;
			goto done;
		}

		/* Cancel the disable clk work */
		WCD_SPI_MUTEX_UNLOCK(spi, wcd_spi->clk_mutex);
		cancel_delayed_work_sync(&wcd_spi->clk_dwork);
@@ -899,6 +964,17 @@ static int wdsp_spi_event_handler(struct device *dev, void *priv_data,
		ret = wdsp_spi_read_section(spi, data);
		break;

	case WDSP_EVENT_SUSPEND:
		WCD_SPI_MUTEX_LOCK(spi, wcd_spi->clk_mutex);
		if (!wcd_spi_can_suspend(wcd_spi))
			ret = -EBUSY;
		WCD_SPI_MUTEX_UNLOCK(spi, wcd_spi->clk_mutex);
		break;

	case WDSP_EVENT_RESUME:
		ret = wcd_spi_wait_for_resume(wcd_spi);
		break;

	default:
		dev_dbg(&spi->dev, "%s: Unhandled event %d\n",
			__func__, event);
@@ -1303,6 +1379,7 @@ static int wcd_spi_probe(struct spi_device *spi)
	mutex_init(&wcd_spi->clk_mutex);
	mutex_init(&wcd_spi->xfer_mutex);
	INIT_DELAYED_WORK(&wcd_spi->clk_dwork, wcd_spi_clk_work);
	init_completion(&wcd_spi->resume_comp);

	wcd_spi->spi = spi;
	spi_set_drvdata(spi, wcd_spi);
@@ -1340,6 +1417,61 @@ static int wcd_spi_remove(struct spi_device *spi)
	return 0;
}

#ifdef CONFIG_PM
static int wcd_spi_suspend(struct device *dev)
{
	struct spi_device *spi = to_spi_device(dev);
	struct wcd_spi_priv *wcd_spi = spi_get_drvdata(spi);
	int rc = 0;

	WCD_SPI_MUTEX_LOCK(spi, wcd_spi->clk_mutex);
	if (!wcd_spi_can_suspend(wcd_spi)) {
		rc = -EBUSY;
		goto done;
	}

	/*
	 * If we are here, it is okay to let the suspend go
	 * through for this driver. But, still need to notify
	 * the master to make sure all other components can suspend
	 * as well.
	 */
	if (wcd_spi->m_dev && wcd_spi->m_ops &&
	  wcd_spi->m_ops->suspend) {
		WCD_SPI_MUTEX_UNLOCK(spi, wcd_spi->clk_mutex);
		rc = wcd_spi->m_ops->suspend(wcd_spi->m_dev);
		WCD_SPI_MUTEX_LOCK(spi, wcd_spi->clk_mutex);
	}

	if (rc == 0)
		set_bit(WCD_SPI_IS_SUSPENDED, &wcd_spi->status_mask);
	else
		dev_dbg(&spi->dev, "%s: cannot suspend, err = %d\n",
			__func__, rc);
done:
	WCD_SPI_MUTEX_UNLOCK(spi, wcd_spi->clk_mutex);
	return rc;
}

static int wcd_spi_resume(struct device *dev)
{
	struct spi_device *spi = to_spi_device(dev);
	struct wcd_spi_priv *wcd_spi = spi_get_drvdata(spi);

	WCD_SPI_MUTEX_LOCK(spi, wcd_spi->clk_mutex);
	clear_bit(WCD_SPI_IS_SUSPENDED, &wcd_spi->status_mask);
	complete(&wcd_spi->resume_comp);
	WCD_SPI_MUTEX_UNLOCK(spi, wcd_spi->clk_mutex);

	return 0;
}

static const struct dev_pm_ops wcd_spi_pm_ops = {
	.suspend = wcd_spi_suspend,
	.resume = wcd_spi_resume,
};
#endif

static const struct of_device_id wcd_spi_of_match[] = {
	{ .compatible = "qcom,wcd-spi-v2", },
	{ }
@@ -1350,6 +1482,9 @@ static struct spi_driver wcd_spi_driver = {
	.driver = {
		.name = "wcd-spi-v2",
		.of_match_table = wcd_spi_of_match,
#ifdef CONFIG_PM
		.pm = &wcd_spi_pm_ops,
#endif
	},
	.probe = wcd_spi_probe,
	.remove = wcd_spi_remove,