Loading Documentation/devicetree/bindings/regulator/mem-acc-regulator.txt +22 −4 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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>; Loading @@ -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>; }; drivers/regulator/mem-acc-regulator.c +136 −0 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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) { Loading @@ -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]; Loading Loading @@ -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) { Loading Loading @@ -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"); Loading Loading
Documentation/devicetree/bindings/regulator/mem-acc-regulator.txt +22 −4 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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>; Loading @@ -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>; };
drivers/regulator/mem-acc-regulator.c +136 −0 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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) { Loading @@ -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]; Loading Loading @@ -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) { Loading Loading @@ -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"); Loading