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

Commit 60634b11 authored by Sujit Reddy Thumma's avatar Sujit Reddy Thumma
Browse files

scsi: ufs-msm: Add bus bandwidth voting support



The UFS host controller on MSM chipsets transfer data over
System NoC to the DDR memory. Add bus bandwidth voting support
based on the speed modes the host communicates with the device
so as to provide optimum throughput while transferring data over
the bus.

Change-Id: I1b407975984985fa108aa9373e2eab08b9027df4
Signed-off-by: default avatarSujit Reddy Thumma <sthumma@codeaurora.org>
parent 1fd0f8cc
Loading
Loading
Loading
Loading
+62 −1
Original line number Diff line number Diff line
@@ -54,7 +54,8 @@ Example:
		max-clock-frequency-hz = <100000000 19200000 0>;
	};

For UFS host controller in MSM platform following clocks are required -
==== MSM UFS platform driver properties =====
* For UFS host controller in MSM platform following clocks are required -
    Controller clock source -
        "core_clk_src", max-clock-frequency-hz = 200MHz

@@ -73,3 +74,63 @@ For UFS host controller in MSM platform following clocks are required -

    Optional reference clock input to UFS device
        "ref_clk", max-clock-frequency-hz = 19.2MHz

* Following bus parameters are required -
- qcom,msm-bus,name
- qcom,msm-bus,num-cases
- qcom,msm-bus,num-paths
- qcom,msm-bus,vectors-KBps
For the above four properties please refer to
Documentation/devicetree/bindings/arm/msm/msm_bus.txt
Note: The instantaneous bandwidth (IB) value in the vectors-KBps field should
      be zero as UFS data transfer path doesn't have latency requirements and
      voting for aggregated bandwidth (AB) should take care of providing
      optimum throughput requested.

- qcom,bus-vector-names: specifies string IDs for the corresponding
bus vectors in the same order as qcom,msm-bus,vectors-KBps property.

Example:
	ufshc@0xfc598000 {
		...

		qcom,msm-bus,name = "ufs1";
		qcom,msm-bus,num-cases = <22>;
		qcom,msm-bus,num-paths = <2>;
		qcom,msm-bus,vectors-KBps =
				<95 512 0 0>, <1 650 0 0>,         /* No vote */

				<95 512 922 0>, <1 650 1000 0>,   /* PWM G1 */
				<95 512 1844 0>, <1 650 1000 0>, /* PWM G2 */
				<95 512 3688 0>, <1 650 1000 0>, /* PWM G3 */
				<95 512 7376 0>, <1 650 1000 0>,  /* PWM G4 */
				<95 512 1844 0>, <1 650 1000 0>, /* PWM G1 L2 */
				<95 512 3688 0>, <1 650 1000 0>, /* PWM G2 L2 */
				<95 512 7376 0>, <1 650 1000 0>,  /* PWM G3 L2 */
				<95 512 14752 0>, <1 650 1000 0>,  /* PWM G4 L2 */

				<95 512 127796 0>, <1 650 1000 0>,  /* HS G1 RA */
				<95 512 255591 0>, <1 650 1000 0>, /* HS G2 RA */
				<95 512 511181 0>, <1 650 1000 0>, /* HS G3 RA */
				<95 512 255591 0>, <1 650 1000 0>, /* HS G1 RA L2 */
				<95 512 511181 0>, <1 650 1000 0>, /* HS G2 RA L2 */
				<95 512 1022362 0>, <1 650 1000 0>, /* HS G3 RA L2 */

				<95 512 149422 0>, <1 650 1000 0>,  /* HS G1 RB */
				<95 512 298189 0>, <1 650 1000 0>, /* HS G2 RB */
				<95 512 596378 0>, <1 650 1000 0>, /* HS G3 RB */
				<95 512 298189 0>, <1 650 1000 0>, /* HS G1 RB L2 */
				<95 512 596378 0>, <1 650 1000 0>, /* HS G2 RB L2 */
				<95 512 1192756 0>, <1 650 1000 0>, /* HS G3 RB L2 */

				<95 512 4096000 0>, <1 650 1000 0>; /* Max. bandwidth */

		qcom,bus-vector-names = "MIN",
					"PWM_G1_L1", "PWM_G2_L1", "PWM_G3_L1", "PWM_G4_L1",
					"PWM_G1_L2", "PWM_G2_L2", "PWM_G3_L2", "PWM_G4_L2",
					"HS_RA_G1_L1", "HS_RA_G2_L1", "HS_RA_G3_L1",
					"HS_RA_G1_L2", "HS_RA_G2_L2", "HS_RA_G3_L2",
					"HS_RB_G1_L1", "HS_RB_G2_L1", "HS_RB_G3_L1",
					"HS_RB_G1_L2", "HS_RB_G2_L2", "HS_RB_G3_L2",
					"MAX";
	};
+40 −0
Original line number Diff line number Diff line
@@ -2016,6 +2016,46 @@
		max-clock-frequency-hz = <200000000>, <0>, <0>, <0>,
						<0>, <0>, <0>, <0>, <0>;

		qcom,msm-bus,name = "ufs1";
		qcom,msm-bus,num-cases = <22>;
		qcom,msm-bus,num-paths = <2>;
		qcom,msm-bus,vectors-KBps =
				<95 512 0 0>, <1 650 0 0>,         /* No vote */

				<95 512 922 0>, <1 650 1000 0>,   /* PWM G1 */
				<95 512 1844 0>, <1 650 1000 0>, /* PWM G2 */
				<95 512 3688 0>, <1 650 1000 0>, /* PWM G3 */
				<95 512 7376 0>, <1 650 1000 0>,  /* PWM G4 */
				<95 512 1844 0>, <1 650 1000 0>, /* PWM G1 L2 */
				<95 512 3688 0>, <1 650 1000 0>, /* PWM G2 L2 */
				<95 512 7376 0>, <1 650 1000 0>,  /* PWM G3 L2 */
				<95 512 14752 0>, <1 650 1000 0>,  /* PWM G4 L2 */

				<95 512 127796 0>, <1 650 1000 0>,  /* HS G1 RA */
				<95 512 255591 0>, <1 650 1000 0>, /* HS G2 RA */
				<95 512 511181 0>, <1 650 1000 0>, /* HS G3 RA */
				<95 512 255591 0>, <1 650 1000 0>, /* HS G1 RA L2 */
				<95 512 511181 0>, <1 650 1000 0>, /* HS G2 RA L2 */
				<95 512 1022362 0>, <1 650 1000 0>, /* HS G3 RA L2 */

				<95 512 149422 0>, <1 650 1000 0>,  /* HS G1 RB */
				<95 512 298189 0>, <1 650 1000 0>, /* HS G2 RB */
				<95 512 596378 0>, <1 650 1000 0>, /* HS G3 RB */
				<95 512 298189 0>, <1 650 1000 0>, /* HS G1 RB L2 */
				<95 512 596378 0>, <1 650 1000 0>, /* HS G2 RB L2 */
				<95 512 1192756 0>, <1 650 1000 0>, /* HS G3 RB L2 */

				<95 512 4096000 0>, <1 650 1000 0>; /* Max. bandwidth */

		qcom,bus-vector-names = "MIN",
					"PWM_G1_L1", "PWM_G2_L1", "PWM_G3_L1", "PWM_G4_L1",
					"PWM_G1_L2", "PWM_G2_L2", "PWM_G3_L2", "PWM_G4_L2",
					"HS_RA_G1_L1", "HS_RA_G2_L1", "HS_RA_G3_L1",
					"HS_RA_G1_L2", "HS_RA_G2_L2", "HS_RA_G3_L2",
					"HS_RB_G1_L1", "HS_RB_G2_L1", "HS_RB_G3_L1",
					"HS_RB_G1_L2", "HS_RB_G2_L2", "HS_RB_G3_L2",
					"MAX";

		status = "disabled";
	};

+296 −42
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@
#include <linux/of.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>

#include <mach/msm_bus.h>

#include "ufshcd.h"
#include "unipro.h"

@@ -28,9 +31,7 @@
#define TX_FSM_HIBERN8          0x1
#define HBRN8_POLL_TOUT_MS      100
#define DEFAULT_CLK_RATE_HZ     1000000

static unsigned long
msm_ufs_cfg_timers(struct ufs_hba *hba, u32 gear, u32 hs);
#define BUS_VECTOR_NAME_LEN     32

/* MSM UFS host controller vendor specific registers */
enum {
@@ -58,6 +59,58 @@ enum {
	MASK_CLK_NS_REG                     = 0xFFFC00,
};

static LIST_HEAD(phy_list);

struct msm_ufs_phy_calibration {
	u32 reg_offset;
	u32 cfg_value;
};

struct msm_ufs_phy_vreg {
	const char *name;
	struct regulator *reg;
	int max_uA;
	int min_uV;
	int max_uV;
	bool enabled;
};

struct msm_ufs_phy {
	struct list_head list;
	struct device *dev;
	void __iomem *mmio;
	struct clk *tx_iface_clk;
	struct clk *rx_iface_clk;
	bool is_iface_clk_enabled;
	struct clk *ref_clk_src;
	struct clk *ref_clk_parent;
	struct clk *ref_clk;
	bool is_ref_clk_enabled;
	struct msm_ufs_phy_vreg vdda_pll;
	struct msm_ufs_phy_vreg vdda_phy;
};

struct msm_ufs_bus_vote {
	uint32_t client_handle;
	uint32_t curr_vote;
	int min_bw_vote;
	int max_bw_vote;
	int saved_vote;
	bool is_max_bw_needed;
	struct device_attribute max_bus_bw;
};

struct msm_ufs_host {
	struct msm_ufs_phy *phy;
	struct ufs_hba *hba;
	struct msm_ufs_bus_vote bus_vote;
	struct ufs_pa_layer_attr dev_req_params;
};

static unsigned long
msm_ufs_cfg_timers(struct ufs_hba *hba, u32 gear, u32 hs);
static int msm_ufs_update_bus_bw_vote(struct msm_ufs_host *host);

/* MSM UFS PHY control registers */

#define COM_OFF(x)     (0x000 + x)
@@ -312,37 +365,6 @@ enum {
#define VDDA_PLL_MIN_UV            1800000
#define VDDA_PLL_MAX_UV            1800000

static LIST_HEAD(phy_list);

struct msm_ufs_phy_calibration {
	u32 reg_offset;
	u32 cfg_value;
};

struct msm_ufs_phy_vreg {
	const char *name;
	struct regulator *reg;
	int max_uA;
	int min_uV;
	int max_uV;
	bool enabled;
};

struct msm_ufs_phy {
	struct list_head list;
	struct device *dev;
	void __iomem *mmio;
	struct clk *tx_iface_clk;
	struct clk *rx_iface_clk;
	bool is_iface_clk_enabled;
	struct clk *ref_clk_src;
	struct clk *ref_clk_parent;
	struct clk *ref_clk;
	bool is_ref_clk_enabled;
	struct msm_ufs_phy_vreg vdda_pll;
	struct msm_ufs_phy_vreg vdda_phy;
};

static struct msm_ufs_phy_calibration phy_cal_table[] = {
	{
		.cfg_value = 0x01,
@@ -1171,7 +1193,8 @@ static int msm_ufs_enable_tx_lanes(struct ufs_hba *hba)
	int err;
	u32 tx_lanes;
	u32 val;
	struct msm_ufs_phy *phy = hba->priv;
	struct msm_ufs_host *host = hba->priv;
	struct msm_ufs_phy *phy = host->phy;

	err = ufshcd_dme_get(hba,
			UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), &tx_lanes);
@@ -1298,7 +1321,8 @@ static inline void msm_ufs_deassert_reset(struct ufs_hba *hba)

static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status)
{
	struct msm_ufs_phy *phy = hba->priv;
	struct msm_ufs_host *host = hba->priv;
	struct msm_ufs_phy *phy = host->phy;
	u32 val;
	int err = -EINVAL;

@@ -1365,7 +1389,8 @@ static int msm_ufs_link_startup_notify(struct ufs_hba *hba, bool status)

static int msm_ufs_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
	struct msm_ufs_phy *phy = hba->priv;
	struct msm_ufs_host *host = hba->priv;
	struct msm_ufs_phy *phy = host->phy;
	int ret = 0;

	if (!phy)
@@ -1398,7 +1423,8 @@ out:

static int msm_ufs_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
	struct msm_ufs_phy *phy = hba->priv;
	struct msm_ufs_host *host = hba->priv;
	struct msm_ufs_phy *phy = host->phy;

	if (!phy)
		return 0;
@@ -1675,7 +1701,8 @@ static int msm_ufs_pwr_change_notify(struct ufs_hba *hba,
				     struct ufs_pa_layer_attr *dev_req_params)
{
	int val;
	struct msm_ufs_phy *phy = hba->priv;
	struct msm_ufs_host *host = hba->priv;
	struct msm_ufs_phy *phy = host->phy;
	struct ufs_msm_dev_params ufs_msm_cap;
	int ret = 0;

@@ -1717,6 +1744,11 @@ static int msm_ufs_pwr_change_notify(struct ufs_hba *hba,
		val = ~(MAX_U32 << dev_req_params->lane_tx);
		writel_relaxed(val, phy->mmio + UFS_PHY_TX_LANE_ENABLE);
		mb();

		/* cache the power mode parameters to use internally */
		memcpy(&host->dev_req_params,
				dev_req_params, sizeof(*dev_req_params));
		msm_ufs_update_bus_bw_vote(host);
		break;
	default:
		ret = -EINVAL;
@@ -1767,6 +1799,204 @@ static void msm_ufs_advertise_quirks(struct ufs_hba *hba)
			      | UFSHCD_QUIRK_BROKEN_SUSPEND);
}

static int msm_ufs_get_bus_vote(struct msm_ufs_host *host,
		const char *speed_mode)
{
	struct device *dev = host->hba->dev;
	struct device_node *np = dev->of_node;
	int err;
	const char *key = "qcom,bus-vector-names";

	if (!speed_mode) {
		err = -EINVAL;
		goto out;
	}

	if (host->bus_vote.is_max_bw_needed && !!strcmp(speed_mode, "MIN"))
		err = of_property_match_string(np, key, "MAX");
	else
		err = of_property_match_string(np, key, speed_mode);

out:
	if (err < 0)
		dev_err(dev, "%s: Invalid %s mode %d\n",
				__func__, speed_mode, err);
	return err;
}

static int msm_ufs_set_bus_vote(struct msm_ufs_host *host, int vote)
{
	int err = 0;

	if (vote != host->bus_vote.curr_vote) {
		err = msm_bus_scale_client_update_request(
				host->bus_vote.client_handle, vote);
		if (err) {
			dev_err(host->hba->dev,
				"%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n",
				__func__, host->bus_vote.client_handle,
				vote, err);
			goto out;
		}

		host->bus_vote.curr_vote = vote;
	}
out:
	return err;
}

static int msm_ufs_get_speed_mode(struct ufs_pa_layer_attr *p, char *result)
{
	int err = 0;
	int gear = max_t(u32, p->gear_rx, p->gear_tx);
	int lanes = max_t(u32, p->lane_rx, p->lane_tx);
	int pwr = max_t(u32, map_unmap_pwr_mode(p->pwr_rx, true),
			map_unmap_pwr_mode(p->pwr_tx, true));

	/* default to PWM Gear 1, Lane 1 if power mode is not initialized */
	if (!gear)
		gear = 1;

	if (!lanes)
		lanes = 1;

	if (!p->pwr_rx && !p->pwr_tx)
		pwr = 0;

	pwr = map_unmap_pwr_mode(pwr, false);
	if (pwr < 0) {
		err = pwr;
		goto out;
	}

	if (pwr == FAST_MODE || pwr == FASTAUTO_MODE)
		snprintf(result, BUS_VECTOR_NAME_LEN, "%s_R%s_G%d_L%d", "HS",
				p->hs_rate == PA_HS_MODE_B ? "B" : "A",
				gear, lanes);
	else
		snprintf(result, BUS_VECTOR_NAME_LEN, "%s_G%d_L%d",
				"PWM", gear, lanes);
out:
	return err;
}

static int msm_ufs_update_bus_bw_vote(struct msm_ufs_host *host)
{
	int vote;
	int err = 0;
	char mode[BUS_VECTOR_NAME_LEN];

	err = msm_ufs_get_speed_mode(&host->dev_req_params, mode);
	if (err)
		goto out;

	vote = msm_ufs_get_bus_vote(host, mode);
	if (vote >= 0)
		err = msm_ufs_set_bus_vote(host, vote);
	else
		err = vote;

out:
	if (err)
		dev_err(host->hba->dev, "%s: failed %d\n", __func__, err);
	else
		host->bus_vote.saved_vote = vote;
	return err;
}

static int msm_ufs_setup_clocks(struct ufs_hba *hba, bool on)
{
	struct msm_ufs_host *host = hba->priv;
	int err;
	int vote;

	if (on) {
		vote = host->bus_vote.saved_vote;
		if (vote == host->bus_vote.min_bw_vote)
			msm_ufs_update_bus_bw_vote(host);
	} else {
		vote = host->bus_vote.min_bw_vote;
	}

	err = msm_ufs_set_bus_vote(host, vote);
	if (err)
		dev_err(hba->dev, "%s: set bus vote failed %d\n",
				__func__, err);

	return err;
}

static ssize_t
show_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute *attr,
			char *buf)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);
	struct msm_ufs_host *host = hba->priv;

	return snprintf(buf, PAGE_SIZE, "%u\n",
			host->bus_vote.is_max_bw_needed);
}

static ssize_t
store_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
{
	struct ufs_hba *hba = dev_get_drvdata(dev);
	struct msm_ufs_host *host = hba->priv;
	uint32_t value;

	if (!kstrtou32(buf, 0, &value)) {
		host->bus_vote.is_max_bw_needed = !!value;
		msm_ufs_update_bus_bw_vote(host);
	}

	return count;
}

static int msm_ufs_bus_register(struct msm_ufs_host *host)
{
	int err;
	struct msm_bus_scale_pdata *bus_pdata;
	struct device *dev = host->hba->dev;
	struct platform_device *pdev = to_platform_device(dev);
	struct device_node *np = dev->of_node;

	bus_pdata = msm_bus_cl_get_pdata(pdev);
	if (!bus_pdata) {
		dev_err(dev, "%s: failed to get bus vectors\n", __func__);
		err = -ENODATA;
		goto out;
	}

	err = of_property_count_strings(np, "qcom,bus-vector-names");
	if (err < 0 || err != bus_pdata->num_usecases) {
		dev_err(dev, "%s: qcom,bus-vector-names not specified correctly %d\n",
				__func__, err);
		goto out;
	}

	host->bus_vote.client_handle = msm_bus_scale_register_client(bus_pdata);
	if (!host->bus_vote.client_handle) {
		dev_err(dev, "%s: msm_bus_scale_register_client failed\n",
				__func__);
		err = -EFAULT;
		goto out;
	}

	/* cache the vote index for minimum and maximum bandwidth */
	host->bus_vote.min_bw_vote = msm_ufs_get_bus_vote(host, "MIN");
	host->bus_vote.max_bw_vote = msm_ufs_get_bus_vote(host, "MAX");

	host->bus_vote.max_bus_bw.show = show_ufs_to_mem_max_bus_bw;
	host->bus_vote.max_bus_bw.store = store_ufs_to_mem_max_bus_bw;
	sysfs_attr_init(&host->bus_vote.max_bus_bw.attr);
	host->bus_vote.max_bus_bw.attr.name = "max_bus_bw";
	host->bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR;
	err = device_create_file(dev, &host->bus_vote.max_bus_bw);
out:
	return err;
}

/**
 * msm_ufs_init - bind phy with controller
 * @hba: host controller instance
@@ -1780,18 +2010,33 @@ static void msm_ufs_advertise_quirks(struct ufs_hba *hba)
static int msm_ufs_init(struct ufs_hba *hba)
{
	int err;
	struct device *dev = hba->dev;
	struct msm_ufs_phy *phy = msm_get_ufs_phy(hba->dev);
	struct msm_ufs_host *host;

	if (IS_ERR(phy)) {
		err = PTR_ERR(phy);
		goto out;
	}

	hba->priv = (void *)phy;
	host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
	if (!host) {
		err = -ENOMEM;
		dev_err(dev, "%s: no memory for msm ufs host\n", __func__);
		goto out;
	}

	host->phy = phy;
	host->hba = hba;
	hba->priv = (void *)host;

	err = msm_ufs_bus_register(host);
	if (err)
		goto out_host_free;

	err = msm_ufs_phy_power_on(phy);
	if (err)
		hba->priv = NULL;
		goto out_host_free;

	msm_ufs_advertise_quirks(hba);
	if (hba->quirks & UFSHCD_QUIRK_BROKEN_SUSPEND) {
@@ -1819,14 +2064,22 @@ static int msm_ufs_init(struct ufs_hba *hba)
		hba->rpm_lvl = UFS_PM_LVL_3;
		hba->spm_lvl = UFS_PM_LVL_3;
	}

out_host_free:
	if (err) {
		devm_kfree(dev, host);
		hba->priv = NULL;
	}
out:
	return err;
}

static void msm_ufs_exit(struct ufs_hba *hba)
{
	struct msm_ufs_phy *phy = hba->priv;
	struct msm_ufs_host *host = hba->priv;
	struct msm_ufs_phy *phy = host->phy;

	msm_bus_scale_unregister_client(host->bus_vote.client_handle);
	msm_ufs_phy_power_off(phy);
}

@@ -1977,6 +2230,7 @@ const struct ufs_hba_variant_ops ufs_hba_msm_vops = {
	.name                   = "msm",
	.init                   = msm_ufs_init,
	.exit                   = msm_ufs_exit,
	.setup_clocks           = msm_ufs_setup_clocks,
	.hce_enable_notify      = msm_ufs_hce_enable_notify,
	.link_startup_notify    = msm_ufs_link_startup_notify,
	.pwr_change_notify	= msm_ufs_pwr_change_notify,