Unverified Commit 80117099 authored by Jon Lin's avatar Jon Lin Committed by Mark Brown
Browse files

spi: rockchip-sfc: Support pm ops



Support system_sleep and runtime_pm ops.

Signed-off-by: default avatarJon Lin <jon.lin@rock-chips.com>
Link: https://patch.msgid.link/20241208130311.1324024-1-jon.lin@rock-chips.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 3f3b063e
Loading
Loading
Loading
Loading
+128 −20
Original line number Diff line number Diff line
@@ -13,12 +13,14 @@
#include <linux/completion.h>
#include <linux/dma-mapping.h>
#include <linux/iopoll.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/spi/spi-mem.h>

/* System control */
@@ -150,11 +152,9 @@
/* Data */
#define SFC_DATA			0x108

/* The controller and documentation reports that it supports up to 4 CS
 * devices (0-3), however I have only been able to test a single CS (CS 0)
 * due to the configuration of my device.
 */
#define SFC_MAX_CHIPSELECT_NUM		4
#define SFC_CS1_REG_OFFSET		0x200

#define SFC_MAX_CHIPSELECT_NUM		2

/* The SFC can transfer max 16KB - 1 at one time
 * we set it to 15.5KB here for alignment.
@@ -169,12 +169,14 @@
 */
#define SFC_MAX_SPEED		(150 * 1000 * 1000)

#define ROCKCHIP_AUTOSUSPEND_DELAY	2000

struct rockchip_sfc {
	struct device *dev;
	void __iomem *regbase;
	struct clk *hclk;
	struct clk *clk;
	u32 frequency;
	u32 speed[SFC_MAX_CHIPSELECT_NUM];
	/* virtual mapped addr for dma_buffer */
	void *buffer;
	dma_addr_t dma_buffer;
@@ -301,6 +303,7 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc,
				   u32 len)
{
	u32 ctrl = 0, cmd = 0;
	u8 cs = spi_get_chipselect(mem->spi, 0);

	/* set CMD */
	cmd = op->cmd.opcode;
@@ -314,7 +317,8 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc,
			cmd |= SFC_CMD_ADDR_24BITS << SFC_CMD_ADDR_SHIFT;
		} else {
			cmd |= SFC_CMD_ADDR_XBITS << SFC_CMD_ADDR_SHIFT;
			writel(op->addr.nbytes * 8 - 1, sfc->regbase + SFC_ABIT);
			writel(op->addr.nbytes * 8 - 1,
			       sfc->regbase + cs * SFC_CS1_REG_OFFSET + SFC_ABIT);
		}

		ctrl |= ((op->addr.buswidth >> 1) << SFC_CTRL_ADDR_BITS_SHIFT);
@@ -346,7 +350,7 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc,

	/* set the Controller */
	ctrl |= SFC_CTRL_PHASE_SEL_NEGETIVE;
	cmd |= spi_get_chipselect(mem->spi, 0) << SFC_CMD_CS_SHIFT;
	cmd |= cs << SFC_CMD_CS_SHIFT;

	dev_dbg(sfc->dev, "sfc addr.nbytes=%x(x%d) dummy.nbytes=%x(x%d)\n",
		op->addr.nbytes, op->addr.buswidth,
@@ -354,7 +358,7 @@ static int rockchip_sfc_xfer_setup(struct rockchip_sfc *sfc,
	dev_dbg(sfc->dev, "sfc ctrl=%x cmd=%x addr=%llx len=%x\n",
		ctrl, cmd, op->addr.val, len);

	writel(ctrl, sfc->regbase + SFC_CTRL);
	writel(ctrl, sfc->regbase + cs * SFC_CS1_REG_OFFSET + SFC_CTRL);
	writel(cmd, sfc->regbase + SFC_CMD);
	if (op->addr.nbytes)
		writel(op->addr.val, sfc->regbase + SFC_ADDR);
@@ -500,14 +504,22 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op
	struct rockchip_sfc *sfc = spi_controller_get_devdata(mem->spi->controller);
	u32 len = op->data.nbytes;
	int ret;
	u8 cs = spi_get_chipselect(mem->spi, 0);

	ret = pm_runtime_get_sync(sfc->dev);
	if (ret < 0) {
		pm_runtime_put_noidle(sfc->dev);
		return ret;
	}

	if (unlikely(mem->spi->max_speed_hz != sfc->frequency) && !has_acpi_companion(sfc->dev)) {
	if (unlikely(mem->spi->max_speed_hz != sfc->speed[cs]) &&
	    !has_acpi_companion(sfc->dev)) {
		ret = clk_set_rate(sfc->clk, mem->spi->max_speed_hz);
		if (ret)
			return ret;
		sfc->frequency = mem->spi->max_speed_hz;
			goto out;
		sfc->speed[cs] = mem->spi->max_speed_hz;
		dev_dbg(sfc->dev, "set_freq=%dHz real_freq=%ldHz\n",
			sfc->frequency, clk_get_rate(sfc->clk));
			sfc->speed[cs], clk_get_rate(sfc->clk));
	}

	rockchip_sfc_adjust_op_work((struct spi_mem_op *)op);
@@ -524,11 +536,17 @@ static int rockchip_sfc_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op
		if (ret != len) {
			dev_err(sfc->dev, "xfer data failed ret %d dir %d\n", ret, op->data.dir);

			return -EIO;
			ret = -EIO;
			goto out;
		}
	}

	return rockchip_sfc_xfer_done(sfc, 100000);
	ret = rockchip_sfc_xfer_done(sfc, 100000);
out:
	pm_runtime_mark_last_busy(sfc->dev);
	pm_runtime_put_autosuspend(sfc->dev);

	return ret;
}

static int rockchip_sfc_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op)
@@ -570,6 +588,7 @@ static int rockchip_sfc_probe(struct platform_device *pdev)
	struct spi_controller *host;
	struct rockchip_sfc *sfc;
	int ret;
	u32 i, val;

	host = devm_spi_alloc_host(&pdev->dev, sizeof(*sfc));
	if (!host)
@@ -602,9 +621,12 @@ static int rockchip_sfc_probe(struct platform_device *pdev)
				     "Failed to get sfc ahb clk\n");

	if (has_acpi_companion(&pdev->dev)) {
		ret = device_property_read_u32(&pdev->dev, "clock-frequency", &sfc->frequency);
		ret = device_property_read_u32(&pdev->dev, "clock-frequency", &val);
		if (ret)
			return dev_err_probe(&pdev->dev, ret, "Failed to find clock-frequency\n");
			return dev_err_probe(&pdev->dev, ret,
					     "Failed to find clock-frequency in ACPI\n");
		for (i = 0; i < SFC_MAX_CHIPSELECT_NUM; i++)
			sfc->speed[i] = val;
	}

	sfc->use_dma = !of_property_read_bool(sfc->dev->of_node, "rockchip,sfc-no-dma");
@@ -646,6 +668,8 @@ static int rockchip_sfc_probe(struct platform_device *pdev)
		goto err_irq;
	}

	platform_set_drvdata(pdev, sfc);

	ret = rockchip_sfc_init(sfc);
	if (ret)
		goto err_irq;
@@ -653,12 +677,27 @@ static int rockchip_sfc_probe(struct platform_device *pdev)
	sfc->max_iosize = rockchip_sfc_get_max_iosize(sfc);
	sfc->version = rockchip_sfc_get_version(sfc);

	ret = spi_register_controller(host);
	pm_runtime_set_autosuspend_delay(dev, ROCKCHIP_AUTOSUSPEND_DELAY);
	pm_runtime_use_autosuspend(dev);
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);
	pm_runtime_get_noresume(dev);

	ret = devm_spi_register_controller(dev, host);
	if (ret)
		goto err_irq;
		goto err_pm_runtime_free;

	pm_runtime_mark_last_busy(dev);
	pm_runtime_put_autosuspend(dev);

	return 0;

err_pm_runtime_free:
	pm_runtime_get_sync(dev);
	pm_runtime_put_noidle(dev);
	pm_runtime_disable(dev);
	pm_runtime_set_suspended(dev);
	pm_runtime_dont_use_autosuspend(dev);
err_irq:
	clk_disable_unprepare(sfc->clk);
err_clk:
@@ -678,6 +717,74 @@ static void rockchip_sfc_remove(struct platform_device *pdev)
	clk_disable_unprepare(sfc->hclk);
}

#ifdef CONFIG_PM
static int rockchip_sfc_runtime_suspend(struct device *dev)
{
	struct rockchip_sfc *sfc = dev_get_drvdata(dev);

	clk_disable_unprepare(sfc->clk);
	clk_disable_unprepare(sfc->hclk);

	return 0;
}

static int rockchip_sfc_runtime_resume(struct device *dev)
{
	struct rockchip_sfc *sfc = dev_get_drvdata(dev);
	int ret;

	ret = clk_prepare_enable(sfc->hclk);
	if (ret < 0)
		return ret;

	ret = clk_prepare_enable(sfc->clk);
	if (ret < 0)
		clk_disable_unprepare(sfc->hclk);

	return ret;
}
#endif /* CONFIG_PM */

#ifdef CONFIG_PM_SLEEP
static int rockchip_sfc_suspend(struct device *dev)
{
	pinctrl_pm_select_sleep_state(dev);

	return pm_runtime_force_suspend(dev);
}

static int rockchip_sfc_resume(struct device *dev)
{
	struct rockchip_sfc *sfc = dev_get_drvdata(dev);
	int ret;

	ret = pm_runtime_force_resume(dev);
	if (ret < 0)
		return ret;

	pinctrl_pm_select_default_state(dev);

	ret = pm_runtime_get_sync(dev);
	if (ret < 0) {
		pm_runtime_put_noidle(dev);
		return ret;
	}

	rockchip_sfc_init(sfc);

	pm_runtime_mark_last_busy(dev);
	pm_runtime_put_autosuspend(dev);

	return 0;
}
#endif /* CONFIG_PM_SLEEP */

static const struct dev_pm_ops rockchip_sfc_pm_ops = {
	SET_RUNTIME_PM_OPS(rockchip_sfc_runtime_suspend,
			   rockchip_sfc_runtime_resume, NULL)
	SET_SYSTEM_SLEEP_PM_OPS(rockchip_sfc_suspend, rockchip_sfc_resume)
};

static const struct of_device_id rockchip_sfc_dt_ids[] = {
	{ .compatible = "rockchip,sfc"},
	{ /* sentinel */ }
@@ -688,6 +795,7 @@ static struct platform_driver rockchip_sfc_driver = {
	.driver = {
		.name	= "rockchip-sfc",
		.of_match_table = rockchip_sfc_dt_ids,
		.pm = &rockchip_sfc_pm_ops,
	},
	.probe	= rockchip_sfc_probe,
	.remove = rockchip_sfc_remove,