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

Commit a7e17c0f authored by Sahitya Tummala's avatar Sahitya Tummala
Browse files

scsi: ufs: Add support for clock scaling using devfreq framework



The clocks for UFS device will be managed by generic DVFS (Dynamic
Voltage and Frequency Scaling) framework within kernel. This devfreq
framework works with different governors to scale the clocks. By default,
UFS devices uses simple_ondemand governor which scales the clocks up if
the load is more than upthreshold and scales down if the load is less than
downthreshold.

Change-Id: I1c3944a5b1771b967c1df3e441a75fd584d6e2a5
Signed-off-by: default avatarSahitya Tummala <stummala@codeaurora.org>
parent 042d7595
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@
config SCSI_UFSHCD
	tristate "Universal Flash Storage Controller Driver Core"
	depends on SCSI && SCSI_DMA
	select PM_DEVFREQ
	select DEVFREQ_GOV_SIMPLE_ONDEMAND
	---help---
	This selects the support for UFS devices in Linux, say Y and make
	  sure that you know the name of your UFS host adapter (the card
+195 −1
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
 *
 * This code is based on drivers/scsi/ufs/ufshcd.c
 * Copyright (C) 2011-2013 Samsung India Software Operations
 * Copyright (c) 2013, The Linux Foundation. All rights reserved.
 * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
 *
 * Authors:
 *	Santosh Yaraganavi <santosh.sy@samsung.com>
@@ -39,6 +39,7 @@

#include <linux/async.h>
#include <scsi/ufs/ioctl.h>
#include <linux/devfreq.h>

#include "ufshcd.h"
#include "unipro.h"
@@ -663,6 +664,8 @@ static void ufshcd_ungate_work(struct work_struct *work)
		hba->clk_gating.is_suspended = false;
	}
unblock_reqs:
	if (ufshcd_is_clkscaling_enabled(hba))
		devfreq_resume_device(hba->devfreq);
	scsi_unblock_requests(hba->host);
}

@@ -773,6 +776,11 @@ static void ufshcd_gate_work(struct work_struct *work)
		ufshcd_set_link_hibern8(hba);
	}

	if (ufshcd_is_clkscaling_enabled(hba)) {
		devfreq_suspend_device(hba->devfreq);
		hba->clk_scaling.window_start_t = 0;
	}

	if (!ufshcd_is_link_active(hba))
		ufshcd_setup_clocks(hba, false);
	else
@@ -878,6 +886,32 @@ static void ufshcd_exit_clk_gating(struct ufs_hba *hba)
	device_remove_file(hba->dev, &hba->clk_gating.delay_attr);
}

/* Must be called with host lock acquired */
static void ufshcd_clk_scaling_start_busy(struct ufs_hba *hba)
{
	if (!ufshcd_is_clkscaling_enabled(hba))
		return;

	if (!hba->clk_scaling.is_busy_started) {
		hba->clk_scaling.busy_start_t = ktime_get();
		hba->clk_scaling.is_busy_started = true;
	}
}

static void ufshcd_clk_scaling_update_busy(struct ufs_hba *hba)
{
	struct ufs_clk_scaling *scaling = &hba->clk_scaling;

	if (!ufshcd_is_clkscaling_enabled(hba))
		return;

	if (!hba->outstanding_reqs && scaling->is_busy_started) {
		scaling->tot_busy_t += ktime_to_us(ktime_sub(ktime_get(),
					scaling->busy_start_t));
		scaling->busy_start_t = ktime_set(0, 0);
		scaling->is_busy_started = false;
	}
}
/**
 * ufshcd_send_command - Send SCSI or device management commands
 * @hba: per adapter instance
@@ -886,6 +920,7 @@ static void ufshcd_exit_clk_gating(struct ufs_hba *hba)
static inline
void ufshcd_send_command(struct ufs_hba *hba, unsigned int task_tag)
{
	ufshcd_clk_scaling_start_busy(hba);
	__set_bit(task_tag, &hba->outstanding_reqs);
	ufshcd_writel(hba, 1 << task_tag, REG_UTP_TRANSFER_REQ_DOOR_BELL);
	UFSHCD_UPDATE_TAG_STATS(hba, task_tag)
@@ -3303,6 +3338,8 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba)
	/* clear corresponding bits of completed commands */
	hba->outstanding_reqs ^= completed_reqs;

	ufshcd_clk_scaling_update_busy(hba);

	/* we might have free'd some tags above */
	wake_up(&hba->dev_cmd.tag_wq);
}
@@ -4390,6 +4427,9 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
		scsi_scan_host(hba->host);
		pm_runtime_put_sync(hba->dev);
	}
	/* Resume devfreq after UFS device is detected */
	if (ufshcd_is_clkscaling_enabled(hba))
		devfreq_resume_device(hba->devfreq);
out:
	/*
	 * If we failed to initialize the device or the device is not
@@ -4914,6 +4954,7 @@ static int ufshcd_init_clocks(struct ufs_hba *hba)
					clki->max_freq, ret);
				goto out;
			}
			clki->curr_freq = clki->max_freq;
		}
		dev_dbg(dev, "%s: clk: %s, rate: %lu\n", __func__,
				clki->name, clk_get_rate(clki->clk));
@@ -5308,6 +5349,11 @@ disable_clks:
	 * host controller trasanction expected till resume.
	 */
	ufshcd_disable_irq(hba);

	if (ufshcd_is_clkscaling_enabled(hba)) {
		devfreq_suspend_device(hba->devfreq);
		hba->clk_scaling.window_start_t = 0;
	}
	goto out;

vops_resume:
@@ -5395,6 +5441,9 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
	ufshcd_disable_auto_bkops(hba);
	hba->clk_gating.is_suspended = false;

	if (ufshcd_is_clkscaling_enabled(hba))
		devfreq_resume_device(hba->devfreq);

	/* Schedule clock gating in case of no access to UFS device yet */
	ufshcd_release(hba);
	goto out;
@@ -5601,6 +5650,8 @@ void ufshcd_remove(struct ufs_hba *hba)
	scsi_host_put(hba->host);

	ufshcd_exit_clk_gating(hba);
	if (ufshcd_is_clkscaling_enabled(hba))
		devfreq_remove_device(hba->devfreq);
	ufshcd_hba_exit(hba);
}
EXPORT_SYMBOL_GPL(ufshcd_remove);
@@ -5665,6 +5716,136 @@ static int ufshcd_set_dma_mask(struct ufs_hba *hba)
	return err;
}

static int ufshcd_scale_clks(struct ufs_hba *hba, bool scale_up)
{
	int ret = 0;
	struct ufs_clk_info *clki;
	struct list_head *head = &hba->clk_list_head;

	if (!head || list_empty(head))
		goto out;

	list_for_each_entry(clki, head, list) {
		if (!IS_ERR_OR_NULL(clki->clk)) {
			if (scale_up && clki->max_freq) {
				if (clki->curr_freq == clki->max_freq)
					continue;
				ret = clk_set_rate(clki->clk, clki->max_freq);
				if (ret) {
					dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n",
						__func__, clki->name,
						clki->max_freq, ret);
					break;
				}
				trace_ufshcd_clk_scaling(dev_name(hba->dev),
						"scaled up", clki->name,
						clki->curr_freq,
						clki->max_freq);
				clki->curr_freq = clki->max_freq;

			} else if (!scale_up && clki->min_freq) {
				if (clki->curr_freq == clki->min_freq)
					continue;
				ret = clk_set_rate(clki->clk, clki->min_freq);
				if (ret) {
					dev_err(hba->dev, "%s: %s clk set rate(%dHz) failed, %d\n",
						__func__, clki->name,
						clki->min_freq, ret);
					break;
				}
				trace_ufshcd_clk_scaling(dev_name(hba->dev),
						"scaled down", clki->name,
						clki->curr_freq,
						clki->min_freq);
				clki->curr_freq = clki->min_freq;
			}
		}
		dev_dbg(hba->dev, "%s: clk: %s, rate: %lu\n", __func__,
				clki->name, clk_get_rate(clki->clk));
	}
	if (hba->vops->clk_scale_notify)
		hba->vops->clk_scale_notify(hba);
out:
	return ret;
}

static int ufshcd_devfreq_target(struct device *dev,
				unsigned long *freq, u32 flags)
{
	int err = 0;
	struct ufs_hba *hba = dev_get_drvdata(dev);

	if (!ufshcd_is_clkscaling_enabled(hba))
		return -EINVAL;

	if (*freq == UINT_MAX)
		err = ufshcd_scale_clks(hba, true);
	else if (*freq == 0)
		err = ufshcd_scale_clks(hba, false);

	return err;
}

static int ufshcd_devfreq_get_dev_status(struct device *dev,
		struct devfreq_dev_status *stat)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);
	struct ufs_clk_scaling *scaling = &hba->clk_scaling;
	unsigned long flags;

	if (!ufshcd_is_clkscaling_enabled(hba))
		return -EINVAL;

	memset(stat, 0, sizeof(*stat));

	spin_lock_irqsave(hba->host->host_lock, flags);
	if (!scaling->window_start_t)
		goto start_window;

	if (scaling->is_busy_started)
		scaling->tot_busy_t += ktime_to_us(ktime_sub(ktime_get(),
					scaling->busy_start_t));

	stat->total_time = jiffies_to_usecs((long)jiffies -
				(long)scaling->window_start_t);
	stat->busy_time = scaling->tot_busy_t;
start_window:
	scaling->window_start_t = jiffies;
	scaling->tot_busy_t = 0;

	if (hba->outstanding_reqs) {
		scaling->busy_start_t = ktime_get();
		scaling->is_busy_started = true;
	} else {
		scaling->busy_start_t = ktime_set(0, 0);
		scaling->is_busy_started = false;
	}
	spin_unlock_irqrestore(hba->host->host_lock, flags);
	return 0;
}

#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND)
static struct devfreq_simple_ondemand_data ufshcd_ondemand_data = {
	.upthreshold = 35,
	.downdifferential = 5,
	.simple_scaling = 1,
};

static const struct devfreq_governor_data ufshcd_governors[] = {
	{ .name = "simple_ondemand", .data = &ufshcd_ondemand_data },
};
#endif

static struct devfreq_dev_profile ufs_devfreq_profile = {
	.polling_ms	= 100,
	.target		= ufshcd_devfreq_target,
	.get_dev_status	= ufshcd_devfreq_get_dev_status,
#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND)
	.governor_data = ufshcd_governors,
	.num_governor_data = ARRAY_SIZE(ufshcd_governors),
#endif
};

/**
 * ufshcd_init - Driver initialization routine
 * @hba: per-adapter instance
@@ -5777,6 +5958,19 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
		goto out_remove_scsi_host;
	}

	if (ufshcd_is_clkscaling_enabled(hba)) {
		hba->devfreq = devfreq_add_device(dev, &ufs_devfreq_profile,
						   "simple_ondemand", NULL);
		if (IS_ERR(hba->devfreq)) {
			dev_err(hba->dev, "Unable to register with devfreq %ld\n",
					PTR_ERR(hba->devfreq));
			goto out_remove_scsi_host;
		}
		/* Suspend devfreq until the UFS device is detected */
		devfreq_suspend_device(hba->devfreq);
		hba->clk_scaling.window_start_t = 0;
	}

	/* Hold auto suspend until async scan completes */
	pm_runtime_get_sync(dev);

+19 −0
Original line number Diff line number Diff line
@@ -229,6 +229,7 @@ struct debugfs_files {
 * @name: clock name
 * @max_freq: maximum frequency supported by the clock
 * @min_freq: min frequency that can be used for clock scaling
 * @curr_freq: indicates the current frequency that it is set to
 * @enabled: variable to check against multiple enable/disable
 */
struct ufs_clk_info {
@@ -237,6 +238,7 @@ struct ufs_clk_info {
	const char *name;
	u32 max_freq;
	u32 min_freq;
	u32 curr_freq;
	bool enabled;
};

@@ -258,6 +260,7 @@ struct ufs_pa_layer_attr {
 * @name: variant name
 * @init: called when the driver is initialized
 * @exit: called to cleanup everything done in init
 * @clk_scale_notify: notifies that clks are scaled up/down
 * @setup_clocks: called before touching any of the controller registers
 * @setup_regulators: called before accessing the host controller
 * @hce_enable_notify: called before and after HCE enable bit is set to allow
@@ -274,6 +277,7 @@ struct ufs_hba_variant_ops {
	const char *name;
	int	(*init)(struct ufs_hba *);
	void    (*exit)(struct ufs_hba *);
	void    (*clk_scale_notify)(struct ufs_hba *);
	int     (*setup_clocks)(struct ufs_hba *, bool);
	int     (*setup_regulators)(struct ufs_hba *, bool);
	int     (*hce_enable_notify)(struct ufs_hba *, bool);
@@ -317,6 +321,13 @@ struct ufs_clk_gating {
	int active_reqs;
};

struct ufs_clk_scaling {
	ktime_t  busy_start_t;
	bool is_busy_started;
	unsigned long  tot_busy_t;
	unsigned long window_start_t;
};

/**
 * struct ufs_hba - per adapter private structure
 * @mmio_base: UFSHCI base register address
@@ -488,7 +499,11 @@ struct ufs_hba {
#define UFSHCD_CAP_CLK_GATING	(1 << 0)
	/* Allow hiberb8 with clk gating */
#define UFSHCD_CAP_HIBERN8_WITH_CLK_GATING (1 << 1)
	/* Allow dynamic clk scaling */
#define UFSHCD_CAP_CLK_SCALING	(1 << 2)

	struct devfreq *devfreq;
	struct ufs_clk_scaling clk_scaling;
#ifdef CONFIG_DEBUG_FS
	struct ufs_stats ufs_stats;
	struct debugfs_files debugfs_files;
@@ -507,6 +522,10 @@ static inline bool ufshcd_can_hibern8_during_gating(struct ufs_hba *hba)
	else
		return false;
}
static inline int ufshcd_is_clkscaling_enabled(struct ufs_hba *hba)
{
	return hba->caps & UFSHCD_CAP_CLK_SCALING;
}
#define ufshcd_writel(hba, val, reg)	\
	writel((val), (hba)->mmio_base + (reg))
#define ufshcd_readl(hba, reg)	\
+29 −1
Original line number Diff line number Diff line
/*
 * Copyright (c) 2013, The Linux Foundation. All rights reserved.
 * Copyright (c) 2013-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
@@ -39,6 +39,34 @@ TRACE_EVENT(ufshcd_clk_gating,
		__get_str(dev_name), __get_str(state))
);

TRACE_EVENT(ufshcd_clk_scaling,

	TP_PROTO(const char *dev_name, const char *state, const char *clk,
		u32 prev_state, u32 curr_state),

	TP_ARGS(dev_name, state, clk, prev_state, curr_state),

	TP_STRUCT__entry(
		__string(dev_name, dev_name)
		__string(state, state)
		__string(clk, clk)
		__field(u32, prev_state)
		__field(u32, curr_state)
	),

	TP_fast_assign(
		__assign_str(dev_name, dev_name);
		__assign_str(state, state);
		__assign_str(clk, clk);
		__entry->prev_state = prev_state;
		__entry->curr_state = curr_state;
	),

	TP_printk("%s: %s %s from %u to %u Hz",
		__get_str(dev_name), __get_str(state), __get_str(clk),
		__entry->prev_state, __entry->curr_state)
);

DECLARE_EVENT_CLASS(ufshcd_template,
	TP_PROTO(const char *dev_name, int err, s64 usecs),