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

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

Merge "regulator: mem-acc-regulator: Skip L1 memory configuration based on fuse"

parents 19f5fb2c 4c4fa4b9
Loading
Loading
Loading
Loading
+22 −4
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@ Required properties:
				[2] maps APC TURBO corner (3) to accelerator TURBO corner

Optional properties:
- reg:				Register addresses for acc-en and acc-sel-l1 acc-sel-l2 control.
- reg-names:			Register names. Must be "acc-sel-l1", "acc-sel-l2", "acc-en".
- reg:				Register addresses for acc-en and acc-sel-l1
				acc-sel-l2 control and MEM ACC eFuse address.
- reg-names:			Register names. Must be "acc-sel-l1",
				"acc-sel-l2", "acc-en", "efuse_addr".
				A given mem-acc-regulator driver must have "acc-sel-l1" or
				"acc-sel-l2" reg-names property and related register address
				property.
@@ -36,11 +38,26 @@ Optional properties:
				is the LSB of a 2-bit value. This 2-bit value
				specifies the corner value used by the
				accelerator for L2 cache.
- qcom,l1-config-skip-fuse-sel:	Array of 5 elements to indicate where to read the bits, what value to
				compare with in order to decide whether to
				skip configuring the L1 accelerator or not while changing the APC corner
				and method to read fuse row, using SCM to read or read register directly.
				The 5 elements with index [0..4] are:
				  [0] => the fuse row number of the selector
				  [1] => LSB bit position of the bits
				  [2] => number of bits
				  [3] => the value to select skip L1 config logic
				  [4] => fuse reading method, 0 for direct reading or 1 for SCM reading
				When the value of the fuse bits specified by first 3 elements equals to
				the value in 4th element, L1 accelerator
				configuration logic is skipped. Otherwise, the original configuration sent
				from corner map should be applied. If the 5th element is 0, read the fuse row
				from register directly. Otherwise, read it through SCM.

mem_acc_vreg_corner: regulator@fd4aa044 {
	compatible = "qcom,mem-acc-regulator";
	reg = <0xfd4aa048 0x1>, <0xfd4aa044 0x1>, <0xfd4af000 0x1>;
	reg-names = "acc-en", "acc-sel-l1" , "acc-sel-l2";
	reg = <0xfd4aa048 0x1>, <0xfd4aa044 0x1>, <0xfd4af000 0x1>, <0x58000 0x1000>;
	reg-names = "acc-en", "acc-sel-l1" , "acc-sel-l2", "efuse_addr";
	regulator-name = "mem_acc_corner";
	regulator-min-microvolt = <1>;
	regulator-max-microvolt = <3>;
@@ -49,4 +66,5 @@ mem_acc_vreg_corner: regulator@fd4aa044 {
	qcom,acc-sel-l1-bit-pos = <0>;
	qcom,acc-sel-l2-bit-pos = <0>;
	qcom,corner-acc-map = <0 1 3>;
	qcom,l1-config-skip-fuse-sel = <0 52 1 1 0>;
};
+136 −0
Original line number Diff line number Diff line
@@ -23,9 +23,15 @@
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <soc/qcom/scm.h>

#define MEM_ACC_SEL_MASK	0x3

#define BYTES_PER_FUSE_ROW	8

/* mem-acc config flags */
#define MEM_ACC_SKIP_L1_CONFIG	BIT(0)

enum {
	MEMORY_L1,
	MEMORY_L2,
@@ -53,8 +59,74 @@ struct mem_acc_regulator {
	void __iomem		*acc_en_base;
	phys_addr_t		acc_sel_addr[MEMORY_MAX];
	phys_addr_t		acc_en_addr;
	u32			flags;

	/* eFuse parameters */
	phys_addr_t		efuse_addr;
	void __iomem		*efuse_base;
};

static u64 mem_acc_read_efuse_row(struct mem_acc_regulator *mem_acc_vreg,
					u32 row_num, bool use_tz_api)
{
	int rc;
	u64 efuse_bits;
	struct mem_acc_read_req {
		u32 row_address;
		int addr_type;
	} req;

	struct mem_acc_read_rsp {
		u32 row_data[2];
		u32 status;
	} rsp;

	if (!use_tz_api) {
		efuse_bits = readq_relaxed(mem_acc_vreg->efuse_base
			+ row_num * BYTES_PER_FUSE_ROW);
		return efuse_bits;
	}

	req.row_address = mem_acc_vreg->efuse_addr +
					row_num * BYTES_PER_FUSE_ROW;
	req.addr_type = 0;
	efuse_bits = 0;

	rc = scm_call(SCM_SVC_FUSE, SCM_FUSE_READ,
			&req, sizeof(req), &rsp, sizeof(rsp));

	if (rc) {
		pr_err("read row %d failed, err code = %d", row_num, rc);
	} else {
		efuse_bits = ((u64)(rsp.row_data[1]) << 32) +
				(u64)rsp.row_data[0];
	}

	return efuse_bits;
}

static int mem_acc_fuse_is_setting_expected(
		struct mem_acc_regulator *mem_acc_vreg, u32 sel_array[5])
{
	u64 fuse_bits;
	u32 ret;

	fuse_bits = mem_acc_read_efuse_row(mem_acc_vreg, sel_array[0],
							sel_array[4]);
	ret = (fuse_bits >> sel_array[1]) & ((1 << sel_array[2]) - 1);
	if (ret == sel_array[3])
		ret = 1;
	else
		ret = 0;

	pr_info("[row:%d] = 0x%llx @%d:%d == %d ?: %s\n",
			sel_array[0], fuse_bits,
			sel_array[1], sel_array[2],
			sel_array[3],
			(ret == 1) ? "yes" : "no");
	return ret;
}

static inline u32 apc_to_acc_corner(struct mem_acc_regulator *mem_acc_vreg,
								int corner)
{
@@ -70,6 +142,14 @@ static void __update_acc_sel(struct mem_acc_regulator *mem_acc_vreg,
{
	u32 acc_data, i, bit, acc_corner;

	/*
	 * Do not configure the L1 ACC corner if the the corresponding flag is
	 * set.
	 */
	if ((mem_type == MEMORY_L1)
			&& (mem_acc_vreg->flags & MEM_ACC_SKIP_L1_CONFIG))
		return;

	acc_data = mem_acc_vreg->acc_sel_reg[mem_type];
	for (i = 0; i < mem_acc_vreg->num_acc_sel[mem_type]; i++) {
		bit = mem_acc_vreg->acc_sel_bit_pos[mem_type][i];
@@ -276,6 +356,56 @@ static int mem_acc_sel_setup(struct mem_acc_regulator *mem_acc_vreg,
	return rc;
}

static int mem_acc_efuse_init(struct platform_device *pdev,
				 struct mem_acc_regulator *mem_acc_vreg)
{
	struct resource *res;
	int len, rc = 0;
	u32 l1_config_skip_fuse_sel[5];

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse_addr");
	if (!res || !res->start) {
		mem_acc_vreg->efuse_base = NULL;
		pr_debug("'efuse_addr' resource missing or not used.\n");
		return 0;
	}

	mem_acc_vreg->efuse_addr = res->start;
	len = res->end - res->start + 1;

	pr_info("efuse_addr = %pa (len=0x%x)\n", &res->start, len);

	mem_acc_vreg->efuse_base = ioremap(mem_acc_vreg->efuse_addr, len);
	if (!mem_acc_vreg->efuse_base) {
		pr_err("Unable to map efuse_addr %pa\n",
				&mem_acc_vreg->efuse_addr);
		return -EINVAL;
	}

	if (of_find_property(mem_acc_vreg->dev->of_node,
				"qcom,l1-config-skip-fuse-sel", NULL)) {
		rc = of_property_read_u32_array(mem_acc_vreg->dev->of_node,
					"qcom,l1-config-skip-fuse-sel",
					l1_config_skip_fuse_sel, 5);
		if (rc < 0) {
			pr_err("Read failed - qcom,l1-config-skip-fuse-sel rc=%d\n",
					rc);
			goto err_out;
		}

		if (mem_acc_fuse_is_setting_expected(mem_acc_vreg,
						l1_config_skip_fuse_sel)) {
			mem_acc_vreg->flags |= MEM_ACC_SKIP_L1_CONFIG;
			pr_debug("Skip L1 configuration enabled\n");
		}
	}


err_out:
	iounmap(mem_acc_vreg->efuse_base);
	return rc;
}

static int mem_acc_init(struct platform_device *pdev,
		struct mem_acc_regulator *mem_acc_vreg)
{
@@ -308,6 +438,12 @@ static int mem_acc_init(struct platform_device *pdev,
		}
	}

	rc = mem_acc_efuse_init(pdev, mem_acc_vreg);
	if (rc) {
		pr_err("Wrong eFuse address specified: rc=%d\n", rc);
		return rc;
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-sel-l1");
	if (!res || !res->start) {
		pr_debug("'acc-sel-l1' resource missing or not used.\n");