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

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

Merge "i2c_qup: Improve implementation of PM callbacks"

parents d54a8298 13983301
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -36,6 +36,10 @@ Optional property:
		      callbacks.
 - qcom,master-id    : Master endpoint number used for voting on clocks using
		      bus-scaling driver.
 - qcom,clk-ctl-xfer : When present, the clocks's state (prepare_enable/
		      unprepare_disable) is controlled by i2c-transaction's
		      begining and ending. When missing, the clock's state
		      is controlled by runtime-pm events.

Example:
	i2c_3: i2c@f9966000 {
@@ -47,6 +51,7 @@ Example:
		interrupt-names = "qup_err_intr";
		qcom,i2c-bus-freq = <100000>;
		qcom,i2c-src-freq = <24000000>;
		qcom,clk-ctl-xfer;
		qcom,scl-gpio = <&msmgpio 7 0>;
		qcom,sda-gpio = <&msmgpio 6 0>;
	};
+5 −0
Original line number Diff line number Diff line
@@ -522,6 +522,10 @@ struct msm_mhl_platform_data {
/**
 * msm_i2c_platform_data: i2c-qup driver configuration data
 *
 * @clk_ctl_xfer : When true, the clocks's state (prepare_enable/
 *       unprepare_disable) is controlled by i2c-transaction's begining and
 *       ending. When false, the clock's state is controlled by runtime-pm
 *       events.
 * @active_only when set, votes when system active and removes the vote when
 *       system goes idle (optimises for performance). When unset, voting using
 *       runtime pm (optimizes for power).
@@ -530,6 +534,7 @@ struct msm_mhl_platform_data {
 */
struct msm_i2c_platform_data {
	int clk_freq;
	bool clk_ctl_xfer;
	uint32_t rmutex;
	const char *rsl_id;
	uint32_t pm_lat;
+155 −119
Original line number Diff line number Diff line
/* Copyright (c) 2009-2013, The Linux Foundation. All rights reserved.
/* Copyright (c) 2009-2014, 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
@@ -85,10 +85,11 @@ enum {
	QUP_MX_INPUT_DONE       = 1U << 11,
};

/* I2C mini core related values */
/* QUP_CONFIG values and flags */
enum {
	I2C_MINI_CORE           = 2U << 8,
	I2C_N_VAL               = 0xF,
	I2C_CORE_CLK_ON_EN      = BIT(13),

};

@@ -129,6 +130,12 @@ enum {
	I2C_CLK_FORCED_LOW_STATE	= 5,
};

enum msm_i2c_state {
	MSM_I2C_PM_ACTIVE,
	MSM_I2C_PM_SUSPENDED,
	MSM_I2C_SYS_SUSPENDING,
	MSM_I2C_SYS_SUSPENDED,
};
#define QUP_MAX_CLK_STATE_RETRIES	300
#define DEFAULT_CLK_RATE		(19200000)
#define I2C_STATUS_CLK_STATE		13
@@ -184,8 +191,7 @@ struct qup_i2c_dev {
	int                          in_blk_sz;
	int                          wr_sz;
	struct msm_i2c_platform_data *pdata;
	int                          suspended;
	int                          pwr_state;
	enum msm_i2c_state           pwr_state;
	struct mutex                 mlock;
	void                         *complete;
	int                          i2c_gpios[ARRAY_SIZE(i2c_rsrcs)];
@@ -339,22 +345,6 @@ qup_update_state(struct qup_i2c_dev *dev, uint32_t state)
	return 0;
}

/*
 * Before calling qup_config_core_on_en(), please make
 * sure that QuPE core is in RESET state.
 */
static void
qup_config_core_on_en(struct qup_i2c_dev *dev)
{
	uint32_t status;

	status = readl_relaxed(dev->base + QUP_CONFIG);
	status |= BIT(13);
	writel_relaxed(status, dev->base + QUP_CONFIG);
	/* making sure that write has really gone through */
	mb();
}

#define MSM_I2C_CLK_PATH_SUSPEND (0)
#define MSM_I2C_CLK_PATH_RESUME  (1)
#define MSM_I2C_CLK_PATH_MAX_BW(dev) ((dev->pdata->src_clk_rate * 8) / 1000)
@@ -500,27 +490,97 @@ static void i2c_qup_clk_path_postponed_register(struct qup_i2c_dev *dev)
	}
}

static void
qup_i2c_pwr_mgmt(struct qup_i2c_dev *dev, unsigned int state)
static int i2c_qup_gpio_request(struct qup_i2c_dev *dev)
{
	dev->pwr_state = state;
	if (state != 0) {
		i2c_qup_clk_path_postponed_register(dev);
		if (!dev->pdata->active_only)
			i2c_qup_clk_path_vote(dev);
	int i;
	int result = 0;

		clk_prepare_enable(dev->clk);
		if (!dev->pdata->keep_ahb_clk_on)
			clk_prepare_enable(dev->pclk);
	} else {
	for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) {
		if (dev->i2c_gpios[i] >= 0) {
			result = gpio_request(dev->i2c_gpios[i], i2c_rsrcs[i]);
			if (result) {
				dev_err(dev->dev,
					"gpio_request for pin %d failed with error %d\n",
					dev->i2c_gpios[i], result);
				goto error;
			}
		}
	}
	return 0;

error:
	for (; --i >= 0;) {
		if (dev->i2c_gpios[i] >= 0)
			gpio_free(dev->i2c_gpios[i]);
	}
	return result;
}

static void i2c_qup_gpio_free(struct qup_i2c_dev *dev)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) {
		if (dev->i2c_gpios[i] >= 0)
			gpio_free(dev->i2c_gpios[i]);
	}
}

static void i2c_qup_pm_suspend_clk(struct qup_i2c_dev *dev)
{
	uint32_t status;

	/* reset core and enable conditional dynamic clock gating */
	qup_update_state(dev, QUP_RESET_STATE);
	status = readl_relaxed(dev->base + QUP_CONFIG);
	status |= I2C_CORE_CLK_ON_EN;
	writel_relaxed(status, dev->base + QUP_CONFIG);
	/* ensure that write has really gone through */
	mb();

	clk_disable_unprepare(dev->clk);
		qup_config_core_on_en(dev);
	if (!dev->pdata->keep_ahb_clk_on)
		clk_disable_unprepare(dev->pclk);
}

static void i2c_qup_pm_resume_clk(struct qup_i2c_dev *dev)
{
	clk_prepare_enable(dev->clk);
	if (!dev->pdata->keep_ahb_clk_on)
		clk_prepare_enable(dev->pclk);
}

static void i2c_qup_pm_suspend(struct qup_i2c_dev *dev)
{
	if (dev->pwr_state == MSM_I2C_PM_SUSPENDED) {
		dev_err(dev->dev, "attempt to suspend when suspended\n");
		return;
	}

	if (!dev->pdata->clk_ctl_xfer)
		i2c_qup_pm_suspend_clk(dev);

	if (!dev->pdata->active_only)
		i2c_qup_clk_path_unvote(dev);

	i2c_qup_gpio_free(dev);
	dev->pwr_state = MSM_I2C_PM_SUSPENDED;
}

static void i2c_qup_pm_resume(struct qup_i2c_dev *dev)
{
	if (dev->pwr_state == MSM_I2C_PM_ACTIVE)
		return;

	i2c_qup_gpio_request(dev);

	i2c_qup_clk_path_postponed_register(dev);
	if (!dev->pdata->active_only)
		i2c_qup_clk_path_vote(dev);

	if (!dev->pdata->clk_ctl_xfer)
		i2c_qup_pm_resume_clk(dev);
	dev->pwr_state = MSM_I2C_PM_ACTIVE;
}

static int
@@ -595,43 +655,6 @@ static int qup_i2c_poll_clock_ready(struct qup_i2c_dev *dev)
	return -ETIMEDOUT;
}

static inline int qup_i2c_request_gpios(struct qup_i2c_dev *dev)
{
	int i;
	int result = 0;

	for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) {
		if (dev->i2c_gpios[i] >= 0) {
			result = gpio_request(dev->i2c_gpios[i], i2c_rsrcs[i]);
			if (result) {
				dev_err(dev->dev,
					"gpio_request for pin %d failed\
					with error %d\n", dev->i2c_gpios[i],
					result);
				goto error;
			}
		}
	}
	return 0;

error:
	for (; --i >= 0;) {
		if (dev->i2c_gpios[i] >= 0)
			gpio_free(dev->i2c_gpios[i]);
	}
	return result;
}

static inline void qup_i2c_free_gpios(struct qup_i2c_dev *dev)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) {
		if (dev->i2c_gpios[i] >= 0)
			gpio_free(dev->i2c_gpios[i]);
	}
}

#ifdef DEBUG
static void qup_verify_fifo(struct qup_i2c_dev *dev, uint32_t val,
				uint32_t addr, int rdwr)
@@ -948,19 +971,35 @@ qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
	long timeout;
	int err;

	/* Alternate if runtime power management is disabled */
	/*
	 * If all slaves of this controller behave as expected, they will
	 * implement suspend and won't call any transaction if they are
	 * suspended. Since controller is its parent, controller's suspend
	 * will be called only AFTER alls slaves are suspended.
	 * However reality is differe and some slave don't implement suspend
	 * If a slave tries to initiate transfer when we are suspended,
	 * pm_runtime_enabled is set to false by system-pm.
	 * Make sure we return error when transaction is initiated while
	 * we are in suspended state
	 */
	mutex_lock(&dev->mlock);
	if (dev->pwr_state >= MSM_I2C_SYS_SUSPENDING) {
		dev_err(dev->dev,
			"xfer not allowed when ctrl is suspended addr:0x%x\n",
			msgs->addr);
		mutex_unlock(&dev->mlock);
		return -EIO;
	}
	if (!pm_runtime_enabled(dev->dev)) {
		dev_dbg(dev->dev, "Runtime PM is disabled\n");
		i2c_qup_pm_resume_runtime(dev->dev);
		dev_dbg(dev->dev, "Runtime PM FEATURE is disabled\n");
		i2c_qup_pm_resume(dev);
	} else {
		pm_runtime_get_sync(dev->dev);
	}
	mutex_lock(&dev->mlock);

	if (dev->suspended) {
		mutex_unlock(&dev->mlock);
		return -EIO;
	}

	if (dev->pdata->clk_ctl_xfer)
		i2c_qup_pm_resume_clk(dev);

	/* Initialize QUP registers during first transfer */
	if (dev->clk_ctl == 0) {
@@ -1263,6 +1302,8 @@ timeout_err:
	dev->pos = 0;
	dev->err = 0;
	dev->cnt = 0;
	if (dev->pdata->clk_ctl_xfer)
		i2c_qup_pm_suspend_clk(dev);
	mutex_unlock(&dev->mlock);
	pm_runtime_mark_last_busy(dev->dev);
	pm_runtime_put_autosuspend(dev->dev);
@@ -1302,6 +1343,7 @@ int msm_i2c_rsrcs_dt_to_pdata_map(struct platform_device *pdev,
	{"qcom,master-id",    &pdata->master_id,    DT_SUGGESTED, DT_U32,   0},
	{"qcom,scl-gpio",      gpios,               DT_OPTIONAL,  DT_GPIO, -1},
	{"qcom,sda-gpio",      gpios + 1,           DT_OPTIONAL,  DT_GPIO, -1},
	{"qcom,clk-ctl-xfer", &pdata->clk_ctl_xfer, DT_OPTIONAL,  DT_BOOL, -1},
	{"qcom,active-only",  &pdata->active_only,  DT_OPTIONAL,  DT_BOOL,  0},
	{NULL,                 NULL,                0,            0,        0},
	};
@@ -1611,7 +1653,7 @@ blsp_core_init:
		pdata->msm_i2c_config_gpio(dev->adapter.nr, 1);

	mutex_init(&dev->mlock);
	dev->pwr_state = 0;
	dev->pwr_state = MSM_I2C_PM_SUSPENDED;
	/* If the same AHB clock is used on Modem side
	 * switch it on here itself and don't switch it
	 * on and off during suspend and resume.
@@ -1685,13 +1727,11 @@ qup_i2c_remove(struct platform_device *pdev)

	/* Grab mutex to ensure ongoing transaction is over */
	mutex_lock(&dev->mlock);
	dev->suspended = 1;
	dev->pwr_state = MSM_I2C_SYS_SUSPENDING;
	mutex_unlock(&dev->mlock);
	i2c_qup_pm_suspend(dev);
	dev->pwr_state = MSM_I2C_SYS_SUSPENDED;
	mutex_destroy(&dev->mlock);
	if (dev->pwr_state != 0) {
		qup_i2c_pwr_mgmt(dev, 0);
		qup_i2c_free_gpios(dev);
	}
	platform_set_drvdata(pdev, NULL);
	if (dev->num_irqs == 3) {
		free_irq(dev->out_irq, dev);
@@ -1732,14 +1772,7 @@ static int i2c_qup_pm_suspend_runtime(struct device *device)
	struct platform_device *pdev = to_platform_device(device);
	struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
	dev_dbg(device, "pm_runtime: suspending...\n");
	/* Grab mutex to ensure ongoing transaction is over */
	mutex_lock(&dev->mlock);
	dev->suspended = 1;
	mutex_unlock(&dev->mlock);
	if (dev->pwr_state != 0) {
		qup_i2c_pwr_mgmt(dev, 0);
		qup_i2c_free_gpios(dev);
	}
	i2c_qup_pm_suspend(dev);
	return 0;
}

@@ -1747,23 +1780,22 @@ static int i2c_qup_pm_resume_runtime(struct device *device)
{
	struct platform_device *pdev = to_platform_device(device);
	struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
	int ret = 0;
	dev_dbg(device, "pm_runtime: resuming...\n");
	if (dev->pwr_state == 0) {
		ret = qup_i2c_request_gpios(dev);
		if (ret != 0)
			return ret;
		qup_i2c_pwr_mgmt(dev, 1);
	}
	dev->suspended = 0;
	i2c_qup_pm_resume(dev);
	return 0;
}

static int qup_i2c_suspend(struct device *device)
static int i2c_qup_pm_suspend_sys(struct device *device)
{
	struct platform_device *pdev = to_platform_device(device);
	struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
	/* Acquire mutex to ensure current transaction is over */
	mutex_lock(&dev->mlock);
	dev->pwr_state = MSM_I2C_SYS_SUSPENDING;
	mutex_unlock(&dev->mlock);
	if (!pm_runtime_enabled(device) || !pm_runtime_suspended(device)) {
		dev_dbg(device, "system suspend");
		i2c_qup_pm_suspend_runtime(device);
		dev_dbg(device, "system suspend\n");
		i2c_qup_pm_suspend(dev);
		/*
		 * set the device's runtime PM status to 'suspended'
		 */
@@ -1771,25 +1803,29 @@ static int qup_i2c_suspend(struct device *device)
		pm_runtime_set_suspended(device);
		pm_runtime_enable(device);
	}
	dev->pwr_state = MSM_I2C_SYS_SUSPENDED;
	return 0;
}

static int qup_i2c_resume(struct device *device)
static int i2c_qup_pm_resume_sys(struct device *device)
{
	struct platform_device *pdev = to_platform_device(device);
	struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
	/*
	 * Rely on runtime-PM to call resume in case it is enabled
	 * Even if it's not enabled, rely on 1st client transaction to do
	 * clock ON and gpio configuration
	 */
	dev_dbg(device, "system resume");
	dev_dbg(device, "system resume\n");
	dev->pwr_state = MSM_I2C_PM_SUSPENDED;
	return 0;
}
#endif /* CONFIG_PM */

static const struct dev_pm_ops i2c_qup_dev_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(
		qup_i2c_suspend,
		qup_i2c_resume
		i2c_qup_pm_suspend_sys,
		i2c_qup_pm_resume_sys
	)
	SET_RUNTIME_PM_OPS(
		i2c_qup_pm_suspend_runtime,