From 0b65c59e3a5475895c93ea5f130597db16b8abf6 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 25 Jun 2018 13:06:13 -0700 Subject: soc: qcom: smem: Correct check for global partition The moved check for the global partition ended up in the wrong place and I failed to spot this in my review. This moves it to the correct place. Fixes: 11d2e7edac6a ("soc: qcom: smem: check sooner in qcom_smem_set_global_partition()") Signed-off-by: Bjorn Andersson Reviewed-by: Alex Elder Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 70b2ee80d6bd..bf4bd71ab53f 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -364,11 +364,6 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, end = phdr_to_last_uncached_entry(phdr); cached = phdr_to_last_cached_entry(phdr); - if (smem->global_partition) { - dev_err(smem->dev, "Already found the global partition\n"); - return -EINVAL; - } - while (hdr < end) { if (hdr->canary != SMEM_PRIVATE_CANARY) goto bad_canary; @@ -736,6 +731,11 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem) bool found = false; int i; + if (smem->global_partition) { + dev_err(smem->dev, "Already found the global partition\n"); + return -EINVAL; + } + ptable = qcom_smem_get_ptable(smem); if (IS_ERR(ptable)) return PTR_ERR(ptable); -- cgit v1.2.3 From a3134fb09e0bc5bee76e13bf863173b86f21cf87 Mon Sep 17 00:00:00 2001 From: Rishabh Bhatnagar Date: Wed, 23 May 2018 17:35:21 -0700 Subject: drivers: soc: Add LLCC driver LLCC (Last Level Cache Controller) provides additional cache memory in the system. LLCC is partitioned into multiple slices and each slice gets its own priority, size, ID and other config parameters. LLCC driver programs these parameters for each slice. Clients that are assigned to use LLCC need to get information such size & ID of the slice they get and activate or deactivate the slice as needed. LLCC driver provides API for the clients to perform these operations. Signed-off-by: Channagoud Kadabi Signed-off-by: Rishabh Bhatnagar Reviewed-by: Evan Green Reviewed-by: Rob Herring Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 17 ++ drivers/soc/qcom/Makefile | 2 + drivers/soc/qcom/llcc-sdm845.c | 94 +++++++++++ drivers/soc/qcom/llcc-slice.c | 335 +++++++++++++++++++++++++++++++++++++ include/linux/soc/qcom/llcc-qcom.h | 180 ++++++++++++++++++++ 5 files changed, 628 insertions(+) create mode 100644 drivers/soc/qcom/llcc-sdm845.c create mode 100644 drivers/soc/qcom/llcc-slice.c create mode 100644 include/linux/soc/qcom/llcc-qcom.h (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 9dc02f390ba3..d1a41852ae7a 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -39,6 +39,23 @@ config QCOM_GSBI functions for connecting the underlying serial UART, SPI, and I2C devices to the output pins. +config QCOM_LLCC + tristate "Qualcomm Technologies, Inc. LLCC driver" + depends on ARCH_QCOM + help + Qualcomm Technologies, Inc. platform specific + Last Level Cache Controller(LLCC) driver. This provides interfaces + to clients that use the LLCC. Say yes here to enable LLCC slice + driver. + +config QCOM_SDM845_LLCC + tristate "Qualcomm Technologies, Inc. SDM845 LLCC driver" + depends on QCOM_LLCC + help + Say yes here to enable the LLCC driver for SDM845. This provides + data required to configure LLCC so that clients can start using the + LLCC slices. + config QCOM_MDT_LOADER tristate select QCOM_SCM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 19dcf957cb3a..5921454c4ddd 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -15,3 +15,5 @@ obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o +obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o +obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.o diff --git a/drivers/soc/qcom/llcc-sdm845.c b/drivers/soc/qcom/llcc-sdm845.c new file mode 100644 index 000000000000..2e1e4f0a5db8 --- /dev/null +++ b/drivers/soc/qcom/llcc-sdm845.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include +#include +#include + +/* + * SCT(System Cache Table) entry contains of the following members: + * usecase_id: Unique id for the client's use case + * slice_id: llcc slice id for each client + * max_cap: The maximum capacity of the cache slice provided in KB + * priority: Priority of the client used to select victim line for replacement + * fixed_size: Boolean indicating if the slice has a fixed capacity + * bonus_ways: Bonus ways are additional ways to be used for any slice, + * if client ends up using more than reserved cache ways. Bonus + * ways are allocated only if they are not reserved for some + * other client. + * res_ways: Reserved ways for the cache slice, the reserved ways cannot + * be used by any other client than the one its assigned to. + * cache_mode: Each slice operates as a cache, this controls the mode of the + * slice: normal or TCM(Tightly Coupled Memory) + * probe_target_ways: Determines what ways to probe for access hit. When + * configured to 1 only bonus and reserved ways are probed. + * When configured to 0 all ways in llcc are probed. + * dis_cap_alloc: Disable capacity based allocation for a client + * retain_on_pc: If this bit is set and client has maintained active vote + * then the ways assigned to this client are not flushed on power + * collapse. + * activate_on_init: Activate the slice immediately after the SCT is programmed + */ +#define SCT_ENTRY(uid, sid, mc, p, fs, bway, rway, cmod, ptw, dca, rp, a) \ + { \ + .usecase_id = uid, \ + .slice_id = sid, \ + .max_cap = mc, \ + .priority = p, \ + .fixed_size = fs, \ + .bonus_ways = bway, \ + .res_ways = rway, \ + .cache_mode = cmod, \ + .probe_target_ways = ptw, \ + .dis_cap_alloc = dca, \ + .retain_on_pc = rp, \ + .activate_on_init = a, \ + } + +static struct llcc_slice_config sdm845_data[] = { + SCT_ENTRY(LLCC_CPUSS, 1, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 1), + SCT_ENTRY(LLCC_VIDSC0, 2, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_VIDSC1, 3, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_ROTATOR, 4, 563, 2, 1, 0x0, 0x00e, 2, 0, 1, 1, 0), + SCT_ENTRY(LLCC_VOICE, 5, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_AUDIO, 6, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_MDMHPGRW, 7, 1024, 2, 0, 0xfc, 0xf00, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_MDM, 8, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_CMPT, 10, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_GPUHTW, 11, 512, 1, 1, 0xc, 0x0, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_GPU, 12, 2304, 1, 0, 0xff0, 0x2, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_MMUHWT, 13, 256, 2, 0, 0x0, 0x1, 0, 0, 1, 0, 1), + SCT_ENTRY(LLCC_CMPTDMA, 15, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_DISP, 16, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_VIDFW, 17, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_MDMHPFX, 20, 1024, 2, 1, 0x0, 0xf00, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_MDMPNG, 21, 1024, 0, 1, 0x1e, 0x0, 0, 0, 1, 1, 0), + SCT_ENTRY(LLCC_AUDHW, 22, 1024, 1, 1, 0xffc, 0x2, 0, 0, 1, 1, 0), +}; + +static int sdm845_qcom_llcc_probe(struct platform_device *pdev) +{ + return qcom_llcc_probe(pdev, sdm845_data, ARRAY_SIZE(sdm845_data)); +} + +static const struct of_device_id sdm845_qcom_llcc_of_match[] = { + { .compatible = "qcom,sdm845-llcc", }, + { } +}; + +static struct platform_driver sdm845_qcom_llcc_driver = { + .driver = { + .name = "sdm845-llcc", + .of_match_table = sdm845_qcom_llcc_of_match, + }, + .probe = sdm845_qcom_llcc_probe, +}; +module_platform_driver(sdm845_qcom_llcc_driver); + +MODULE_DESCRIPTION("QCOM sdm845 LLCC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/llcc-slice.c b/drivers/soc/qcom/llcc-slice.c new file mode 100644 index 000000000000..fcaad1a4b41d --- /dev/null +++ b/drivers/soc/qcom/llcc-slice.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ACTIVATE BIT(0) +#define DEACTIVATE BIT(1) +#define ACT_CTRL_OPCODE_ACTIVATE BIT(0) +#define ACT_CTRL_OPCODE_DEACTIVATE BIT(1) +#define ACT_CTRL_ACT_TRIG BIT(0) +#define ACT_CTRL_OPCODE_SHIFT 0x01 +#define ATTR1_PROBE_TARGET_WAYS_SHIFT 0x02 +#define ATTR1_FIXED_SIZE_SHIFT 0x03 +#define ATTR1_PRIORITY_SHIFT 0x04 +#define ATTR1_MAX_CAP_SHIFT 0x10 +#define ATTR0_RES_WAYS_MASK GENMASK(11, 0) +#define ATTR0_BONUS_WAYS_MASK GENMASK(27, 16) +#define ATTR0_BONUS_WAYS_SHIFT 0x10 +#define LLCC_STATUS_READ_DELAY 100 + +#define CACHE_LINE_SIZE_SHIFT 6 + +#define LLCC_COMMON_STATUS0 0x0003000c +#define LLCC_LB_CNT_MASK GENMASK(31, 28) +#define LLCC_LB_CNT_SHIFT 28 + +#define MAX_CAP_TO_BYTES(n) (n * SZ_1K) +#define LLCC_TRP_ACT_CTRLn(n) (n * SZ_4K) +#define LLCC_TRP_STATUSn(n) (4 + n * SZ_4K) +#define LLCC_TRP_ATTR0_CFGn(n) (0x21000 + SZ_8 * n) +#define LLCC_TRP_ATTR1_CFGn(n) (0x21004 + SZ_8 * n) + +#define BANK_OFFSET_STRIDE 0x80000 + +static struct llcc_drv_data *drv_data; + +static const struct regmap_config llcc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, +}; + +/** + * llcc_slice_getd - get llcc slice descriptor + * @uid: usecase_id for the client + * + * A pointer to llcc slice descriptor will be returned on success and + * and error pointer is returned on failure + */ +struct llcc_slice_desc *llcc_slice_getd(u32 uid) +{ + const struct llcc_slice_config *cfg; + struct llcc_slice_desc *desc; + u32 sz, count; + + cfg = drv_data->cfg; + sz = drv_data->cfg_size; + + for (count = 0; cfg && count < sz; count++, cfg++) + if (cfg->usecase_id == uid) + break; + + if (count == sz || !cfg) + return ERR_PTR(-ENODEV); + + desc = kzalloc(sizeof(*desc), GFP_KERNEL); + if (!desc) + return ERR_PTR(-ENOMEM); + + desc->slice_id = cfg->slice_id; + desc->slice_size = cfg->max_cap; + + return desc; +} +EXPORT_SYMBOL_GPL(llcc_slice_getd); + +/** + * llcc_slice_putd - llcc slice descritpor + * @desc: Pointer to llcc slice descriptor + */ +void llcc_slice_putd(struct llcc_slice_desc *desc) +{ + kfree(desc); +} +EXPORT_SYMBOL_GPL(llcc_slice_putd); + +static int llcc_update_act_ctrl(u32 sid, + u32 act_ctrl_reg_val, u32 status) +{ + u32 act_ctrl_reg; + u32 status_reg; + u32 slice_status; + int ret; + + act_ctrl_reg = drv_data->bcast_off + LLCC_TRP_ACT_CTRLn(sid); + status_reg = drv_data->bcast_off + LLCC_TRP_STATUSn(sid); + + /* Set the ACTIVE trigger */ + act_ctrl_reg_val |= ACT_CTRL_ACT_TRIG; + ret = regmap_write(drv_data->regmap, act_ctrl_reg, act_ctrl_reg_val); + if (ret) + return ret; + + /* Clear the ACTIVE trigger */ + act_ctrl_reg_val &= ~ACT_CTRL_ACT_TRIG; + ret = regmap_write(drv_data->regmap, act_ctrl_reg, act_ctrl_reg_val); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(drv_data->regmap, status_reg, + slice_status, !(slice_status & status), + 0, LLCC_STATUS_READ_DELAY); + return ret; +} + +/** + * llcc_slice_activate - Activate the llcc slice + * @desc: Pointer to llcc slice descriptor + * + * A value of zero will be returned on success and a negative errno will + * be returned in error cases + */ +int llcc_slice_activate(struct llcc_slice_desc *desc) +{ + int ret; + u32 act_ctrl_val; + + mutex_lock(&drv_data->lock); + if (test_bit(desc->slice_id, drv_data->bitmap)) { + mutex_unlock(&drv_data->lock); + return 0; + } + + act_ctrl_val = ACT_CTRL_OPCODE_ACTIVATE << ACT_CTRL_OPCODE_SHIFT; + + ret = llcc_update_act_ctrl(desc->slice_id, act_ctrl_val, + DEACTIVATE); + if (ret) { + mutex_unlock(&drv_data->lock); + return ret; + } + + __set_bit(desc->slice_id, drv_data->bitmap); + mutex_unlock(&drv_data->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(llcc_slice_activate); + +/** + * llcc_slice_deactivate - Deactivate the llcc slice + * @desc: Pointer to llcc slice descriptor + * + * A value of zero will be returned on success and a negative errno will + * be returned in error cases + */ +int llcc_slice_deactivate(struct llcc_slice_desc *desc) +{ + u32 act_ctrl_val; + int ret; + + mutex_lock(&drv_data->lock); + if (!test_bit(desc->slice_id, drv_data->bitmap)) { + mutex_unlock(&drv_data->lock); + return 0; + } + act_ctrl_val = ACT_CTRL_OPCODE_DEACTIVATE << ACT_CTRL_OPCODE_SHIFT; + + ret = llcc_update_act_ctrl(desc->slice_id, act_ctrl_val, + ACTIVATE); + if (ret) { + mutex_unlock(&drv_data->lock); + return ret; + } + + __clear_bit(desc->slice_id, drv_data->bitmap); + mutex_unlock(&drv_data->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(llcc_slice_deactivate); + +/** + * llcc_get_slice_id - return the slice id + * @desc: Pointer to llcc slice descriptor + */ +int llcc_get_slice_id(struct llcc_slice_desc *desc) +{ + return desc->slice_id; +} +EXPORT_SYMBOL_GPL(llcc_get_slice_id); + +/** + * llcc_get_slice_size - return the slice id + * @desc: Pointer to llcc slice descriptor + */ +size_t llcc_get_slice_size(struct llcc_slice_desc *desc) +{ + return desc->slice_size; +} +EXPORT_SYMBOL_GPL(llcc_get_slice_size); + +static int qcom_llcc_cfg_program(struct platform_device *pdev) +{ + int i; + u32 attr1_cfg; + u32 attr0_cfg; + u32 attr1_val; + u32 attr0_val; + u32 max_cap_cacheline; + u32 sz; + int ret; + const struct llcc_slice_config *llcc_table; + struct llcc_slice_desc desc; + u32 bcast_off = drv_data->bcast_off; + + sz = drv_data->cfg_size; + llcc_table = drv_data->cfg; + + for (i = 0; i < sz; i++) { + attr1_cfg = bcast_off + + LLCC_TRP_ATTR1_CFGn(llcc_table[i].slice_id); + attr0_cfg = bcast_off + + LLCC_TRP_ATTR0_CFGn(llcc_table[i].slice_id); + + attr1_val = llcc_table[i].cache_mode; + attr1_val |= llcc_table[i].probe_target_ways << + ATTR1_PROBE_TARGET_WAYS_SHIFT; + attr1_val |= llcc_table[i].fixed_size << + ATTR1_FIXED_SIZE_SHIFT; + attr1_val |= llcc_table[i].priority << + ATTR1_PRIORITY_SHIFT; + + max_cap_cacheline = MAX_CAP_TO_BYTES(llcc_table[i].max_cap); + + /* LLCC instances can vary for each target. + * The SW writes to broadcast register which gets propagated + * to each llcc instace (llcc0,.. llccN). + * Since the size of the memory is divided equally amongst the + * llcc instances, we need to configure the max cap accordingly. + */ + max_cap_cacheline = max_cap_cacheline / drv_data->num_banks; + max_cap_cacheline >>= CACHE_LINE_SIZE_SHIFT; + attr1_val |= max_cap_cacheline << ATTR1_MAX_CAP_SHIFT; + + attr0_val = llcc_table[i].res_ways & ATTR0_RES_WAYS_MASK; + attr0_val |= llcc_table[i].bonus_ways << ATTR0_BONUS_WAYS_SHIFT; + + ret = regmap_write(drv_data->regmap, attr1_cfg, attr1_val); + if (ret) + return ret; + ret = regmap_write(drv_data->regmap, attr0_cfg, attr0_val); + if (ret) + return ret; + if (llcc_table[i].activate_on_init) { + desc.slice_id = llcc_table[i].slice_id; + ret = llcc_slice_activate(&desc); + } + } + return ret; +} + +int qcom_llcc_probe(struct platform_device *pdev, + const struct llcc_slice_config *llcc_cfg, u32 sz) +{ + u32 num_banks; + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *base; + int ret, i; + + drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + drv_data->regmap = devm_regmap_init_mmio(dev, base, + &llcc_regmap_config); + if (IS_ERR(drv_data->regmap)) + return PTR_ERR(drv_data->regmap); + + ret = regmap_read(drv_data->regmap, LLCC_COMMON_STATUS0, + &num_banks); + if (ret) + return ret; + + num_banks &= LLCC_LB_CNT_MASK; + num_banks >>= LLCC_LB_CNT_SHIFT; + drv_data->num_banks = num_banks; + + for (i = 0; i < sz; i++) + if (llcc_cfg[i].slice_id > drv_data->max_slices) + drv_data->max_slices = llcc_cfg[i].slice_id; + + drv_data->offsets = devm_kcalloc(dev, num_banks, sizeof(u32), + GFP_KERNEL); + if (!drv_data->offsets) + return -ENOMEM; + + for (i = 0; i < num_banks; i++) + drv_data->offsets[i] = i * BANK_OFFSET_STRIDE; + + drv_data->bcast_off = num_banks * BANK_OFFSET_STRIDE; + + drv_data->bitmap = devm_kcalloc(dev, + BITS_TO_LONGS(drv_data->max_slices), sizeof(unsigned long), + GFP_KERNEL); + if (!drv_data->bitmap) + return -ENOMEM; + + drv_data->cfg = llcc_cfg; + drv_data->cfg_size = sz; + mutex_init(&drv_data->lock); + platform_set_drvdata(pdev, drv_data); + + return qcom_llcc_cfg_program(pdev); +} +EXPORT_SYMBOL_GPL(qcom_llcc_probe); diff --git a/include/linux/soc/qcom/llcc-qcom.h b/include/linux/soc/qcom/llcc-qcom.h new file mode 100644 index 000000000000..7e3b9c605ab2 --- /dev/null +++ b/include/linux/soc/qcom/llcc-qcom.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + * + */ + +#include +#ifndef __LLCC_QCOM__ +#define __LLCC_QCOM__ + +#define LLCC_CPUSS 1 +#define LLCC_VIDSC0 2 +#define LLCC_VIDSC1 3 +#define LLCC_ROTATOR 4 +#define LLCC_VOICE 5 +#define LLCC_AUDIO 6 +#define LLCC_MDMHPGRW 7 +#define LLCC_MDM 8 +#define LLCC_CMPT 10 +#define LLCC_GPUHTW 11 +#define LLCC_GPU 12 +#define LLCC_MMUHWT 13 +#define LLCC_CMPTDMA 15 +#define LLCC_DISP 16 +#define LLCC_VIDFW 17 +#define LLCC_MDMHPFX 20 +#define LLCC_MDMPNG 21 +#define LLCC_AUDHW 22 + +/** + * llcc_slice_desc - Cache slice descriptor + * @slice_id: llcc slice id + * @slice_size: Size allocated for the llcc slice + */ +struct llcc_slice_desc { + u32 slice_id; + size_t slice_size; +}; + +/** + * llcc_slice_config - Data associated with the llcc slice + * @usecase_id: usecase id for which the llcc slice is used + * @slice_id: llcc slice id assigned to each slice + * @max_cap: maximum capacity of the llcc slice + * @priority: priority of the llcc slice + * @fixed_size: whether the llcc slice can grow beyond its size + * @bonus_ways: bonus ways associated with llcc slice + * @res_ways: reserved ways associated with llcc slice + * @cache_mode: mode of the llcc slice + * @probe_target_ways: Probe only reserved and bonus ways on a cache miss + * @dis_cap_alloc: Disable capacity based allocation + * @retain_on_pc: Retain through power collapse + * @activate_on_init: activate the slice on init + */ +struct llcc_slice_config { + u32 usecase_id; + u32 slice_id; + u32 max_cap; + u32 priority; + bool fixed_size; + u32 bonus_ways; + u32 res_ways; + u32 cache_mode; + u32 probe_target_ways; + bool dis_cap_alloc; + bool retain_on_pc; + bool activate_on_init; +}; + +/** + * llcc_drv_data - Data associated with the llcc driver + * @regmap: regmap associated with the llcc device + * @cfg: pointer to the data structure for slice configuration + * @lock: mutex associated with each slice + * @cfg_size: size of the config data table + * @max_slices: max slices as read from device tree + * @bcast_off: Offset of the broadcast bank + * @num_banks: Number of llcc banks + * @bitmap: Bit map to track the active slice ids + * @offsets: Pointer to the bank offsets array + */ +struct llcc_drv_data { + struct regmap *regmap; + const struct llcc_slice_config *cfg; + struct mutex lock; + u32 cfg_size; + u32 max_slices; + u32 bcast_off; + u32 num_banks; + unsigned long *bitmap; + u32 *offsets; +}; + +#if IS_ENABLED(CONFIG_QCOM_LLCC) +/** + * llcc_slice_getd - get llcc slice descriptor + * @uid: usecase_id of the client + */ +struct llcc_slice_desc *llcc_slice_getd(u32 uid); + +/** + * llcc_slice_putd - llcc slice descritpor + * @desc: Pointer to llcc slice descriptor + */ +void llcc_slice_putd(struct llcc_slice_desc *desc); + +/** + * llcc_get_slice_id - get slice id + * @desc: Pointer to llcc slice descriptor + */ +int llcc_get_slice_id(struct llcc_slice_desc *desc); + +/** + * llcc_get_slice_size - llcc slice size + * @desc: Pointer to llcc slice descriptor + */ +size_t llcc_get_slice_size(struct llcc_slice_desc *desc); + +/** + * llcc_slice_activate - Activate the llcc slice + * @desc: Pointer to llcc slice descriptor + */ +int llcc_slice_activate(struct llcc_slice_desc *desc); + +/** + * llcc_slice_deactivate - Deactivate the llcc slice + * @desc: Pointer to llcc slice descriptor + */ +int llcc_slice_deactivate(struct llcc_slice_desc *desc); + +/** + * qcom_llcc_probe - program the sct table + * @pdev: platform device pointer + * @table: soc sct table + * @sz: Size of the config table + */ +int qcom_llcc_probe(struct platform_device *pdev, + const struct llcc_slice_config *table, u32 sz); +#else +static inline struct llcc_slice_desc *llcc_slice_getd(u32 uid) +{ + return NULL; +} + +static inline void llcc_slice_putd(struct llcc_slice_desc *desc) +{ + +}; + +static inline int llcc_get_slice_id(struct llcc_slice_desc *desc) +{ + return -EINVAL; +} + +static inline size_t llcc_get_slice_size(struct llcc_slice_desc *desc) +{ + return 0; +} +static inline int llcc_slice_activate(struct llcc_slice_desc *desc) +{ + return -EINVAL; +} + +static inline int llcc_slice_deactivate(struct llcc_slice_desc *desc) +{ + return -EINVAL; +} +static inline int qcom_llcc_probe(struct platform_device *pdev, + const struct llcc_slice_config *table, u32 sz) +{ + return -ENODEV; +} + +static inline int qcom_llcc_remove(struct platform_device *pdev) +{ + return -ENODEV; +} +#endif + +#endif -- cgit v1.2.3 From 658628e7ef78e875cfe13064387c1a7a287d6338 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:56:58 +0530 Subject: drivers: qcom: rpmh-rsc: add RPMH controller for QCOM SoCs Add controller driver for QCOM SoCs that have hardware based shared resource management. The hardware IP known as RSC (Resource State Coordinator) houses multiple Direct Resource Voter (DRV) for different execution levels. A DRV is a unique voter on the state of a shared resource. A Trigger Control Set (TCS) is a bunch of slots that can house multiple resource state requests, that when triggered will issue those requests through an internal bus to the Resource Power Manager Hardened (RPMH) blocks. These hardware blocks are capable of adjusting clocks, voltages, etc. The resource state request from a DRV are aggregated along with state requests from other processors in the SoC and the aggregate value is applied on the resource. Some important aspects of the RPMH communication - - Requests are with some header information - Multiple requests (upto 16) may be sent through a TCS, at a time - Requests in a TCS are sent in sequence - Requests may be fire-n-forget or completion (response expected) - Multiple TCS from the same DRV may be triggered simultaneously - Cannot send a request if another request for the same addr is in progress from the same DRV - When all the requests from a TCS are complete, an IRQ is raised - The IRQ handler needs to clear the TCS before it is available for reuse - TCS configuration is specific to a DRV - Platform drivers may use DRV from different RSCs to make requests Resource state requests made when CPUs are active are called 'active' state requests. Requests made when all the CPUs are powered down (idle state) are called 'sleep' state requests. They are matched by a corresponding 'wake' state requests which puts the resources back in to previously requested active state before resuming any CPU. TCSes are dedicated for each type of requests. Active mode TCSes (AMC) are used to send requests immediately to the resource, while control TCS are used to provide specific information to the controller. Sleep and Wake TCS send sleep and wake requests, after and before the system halt respectively. Signed-off-by: Lina Iyer Signed-off-by: Raju P.L.S.S.S.N Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 10 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmh-internal.h | 69 +++++ drivers/soc/qcom/rpmh-rsc.c | 481 ++++++++++++++++++++++++++++++++ include/dt-bindings/soc/qcom,rpmh-rsc.h | 14 + include/soc/qcom/tcs.h | 56 ++++ 6 files changed, 631 insertions(+) create mode 100644 drivers/soc/qcom/rpmh-internal.h create mode 100644 drivers/soc/qcom/rpmh-rsc.c create mode 100644 include/dt-bindings/soc/qcom,rpmh-rsc.h create mode 100644 include/soc/qcom/tcs.h (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index d1a41852ae7a..ccbdb398fa63 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -91,6 +91,16 @@ config QCOM_RMTFS_MEM Say y here if you intend to boot the modem remoteproc. +config QCOM_RPMH + bool "Qualcomm RPM-Hardened (RPMH) Communication" + depends on ARCH_QCOM && ARM64 && OF || COMPILE_TEST + help + Support for communication with the hardened-RPM blocks in + Qualcomm Technologies Inc (QTI) SoCs. RPMH communication uses an + internal bus to transmit state requests for shared resources. A set + of hardware components aggregate requests for these resources and + help apply the aggregated state on the resource. + config QCOM_SMEM tristate "Qualcomm Shared Memory Manager (SMEM)" depends on ARCH_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 5921454c4ddd..b2faf9aeed3f 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_QCOM_PM) += spm.o obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o qmi_helpers-y += qmi_encdec.o qmi_interface.o obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o +obj-$(CONFIG_QCOM_RPMH) += rpmh-rsc.o obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o obj-$(CONFIG_QCOM_SMEM) += smem.o obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h new file mode 100644 index 000000000000..cc29176f1303 --- /dev/null +++ b/drivers/soc/qcom/rpmh-internal.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + + +#ifndef __RPM_INTERNAL_H__ +#define __RPM_INTERNAL_H__ + +#include +#include + +#define TCS_TYPE_NR 4 +#define MAX_CMDS_PER_TCS 16 +#define MAX_TCS_PER_TYPE 3 +#define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR) + +struct rsc_drv; + +/** + * struct tcs_group: group of Trigger Command Sets (TCS) to send state requests + * to the controller + * + * @drv: the controller + * @type: type of the TCS in this group - active, sleep, wake + * @mask: mask of the TCSes relative to all the TCSes in the RSC + * @offset: start of the TCS group relative to the TCSes in the RSC + * @num_tcs: number of TCSes in this type + * @ncpt: number of commands in each TCS + * @lock: lock for synchronizing this TCS writes + * @req: requests that are sent from the TCS + */ +struct tcs_group { + struct rsc_drv *drv; + int type; + u32 mask; + u32 offset; + int num_tcs; + int ncpt; + spinlock_t lock; + const struct tcs_request *req[MAX_TCS_PER_TYPE]; +}; + +/** + * struct rsc_drv: the Direct Resource Voter (DRV) of the + * Resource State Coordinator controller (RSC) + * + * @name: controller identifier + * @tcs_base: start address of the TCS registers in this controller + * @id: instance id in the controller (Direct Resource Voter) + * @num_tcs: number of TCSes in this DRV + * @tcs: TCS groups + * @tcs_in_use: s/w state of the TCS + * @lock: synchronize state of the controller + */ +struct rsc_drv { + const char *name; + void __iomem *tcs_base; + int id; + int num_tcs; + struct tcs_group tcs[TCS_TYPE_NR]; + DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR); + spinlock_t lock; +}; + + +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg); + +#endif /* __RPM_INTERNAL_H__ */ diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c new file mode 100644 index 000000000000..c5e0793cdfb7 --- /dev/null +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s " fmt, KBUILD_MODNAME + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rpmh-internal.h" + +#define RSC_DRV_TCS_OFFSET 672 +#define RSC_DRV_CMD_OFFSET 20 + +/* DRV Configuration Information Register */ +#define DRV_PRNT_CHLD_CONFIG 0x0C +#define DRV_NUM_TCS_MASK 0x3F +#define DRV_NUM_TCS_SHIFT 6 +#define DRV_NCPT_MASK 0x1F +#define DRV_NCPT_SHIFT 27 + +/* Register offsets */ +#define RSC_DRV_IRQ_ENABLE 0x00 +#define RSC_DRV_IRQ_STATUS 0x04 +#define RSC_DRV_IRQ_CLEAR 0x08 +#define RSC_DRV_CMD_WAIT_FOR_CMPL 0x10 +#define RSC_DRV_CONTROL 0x14 +#define RSC_DRV_STATUS 0x18 +#define RSC_DRV_CMD_ENABLE 0x1C +#define RSC_DRV_CMD_MSGID 0x30 +#define RSC_DRV_CMD_ADDR 0x34 +#define RSC_DRV_CMD_DATA 0x38 +#define RSC_DRV_CMD_STATUS 0x3C +#define RSC_DRV_CMD_RESP_DATA 0x40 + +#define TCS_AMC_MODE_ENABLE BIT(16) +#define TCS_AMC_MODE_TRIGGER BIT(24) + +/* TCS CMD register bit mask */ +#define CMD_MSGID_LEN 8 +#define CMD_MSGID_RESP_REQ BIT(8) +#define CMD_MSGID_WRITE BIT(16) +#define CMD_STATUS_ISSUED BIT(8) +#define CMD_STATUS_COMPL BIT(16) + +static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id) +{ + return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id + + RSC_DRV_CMD_OFFSET * cmd_id); +} + +static void write_tcs_cmd(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id, + u32 data) +{ + writel_relaxed(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id + + RSC_DRV_CMD_OFFSET * cmd_id); +} + +static void write_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, u32 data) +{ + writel_relaxed(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id); +} + +static void write_tcs_reg_sync(struct rsc_drv *drv, int reg, int tcs_id, + u32 data) +{ + writel(data, drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id); + for (;;) { + if (data == readl(drv->tcs_base + reg + + RSC_DRV_TCS_OFFSET * tcs_id)) + break; + udelay(1); + } +} + +static bool tcs_is_free(struct rsc_drv *drv, int tcs_id) +{ + return !test_bit(tcs_id, drv->tcs_in_use) && + read_tcs_reg(drv, RSC_DRV_STATUS, tcs_id, 0); +} + +static struct tcs_group *get_tcs_of_type(struct rsc_drv *drv, int type) +{ + return &drv->tcs[type]; +} + +static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, + const struct tcs_request *msg) +{ + int type; + + switch (msg->state) { + case RPMH_ACTIVE_ONLY_STATE: + type = ACTIVE_TCS; + break; + default: + return ERR_PTR(-EINVAL); + } + + return get_tcs_of_type(drv, type); +} + +static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv, + int tcs_id) +{ + struct tcs_group *tcs; + int i; + + for (i = 0; i < drv->num_tcs; i++) { + tcs = &drv->tcs[i]; + if (tcs->mask & BIT(tcs_id)) + return tcs->req[tcs_id - tcs->offset]; + } + + return NULL; +} + +/** + * tcs_tx_done: TX Done interrupt handler + */ +static irqreturn_t tcs_tx_done(int irq, void *p) +{ + struct rsc_drv *drv = p; + int i, j; + unsigned long irq_status; + const struct tcs_request *req; + struct tcs_cmd *cmd; + + irq_status = read_tcs_reg(drv, RSC_DRV_IRQ_STATUS, 0, 0); + + for_each_set_bit(i, &irq_status, BITS_PER_LONG) { + req = get_req_from_tcs(drv, i); + if (!req) { + WARN_ON(1); + goto skip; + } + + for (j = 0; j < req->num_cmds; j++) { + u32 sts; + + cmd = &req->cmds[j]; + sts = read_tcs_reg(drv, RSC_DRV_CMD_STATUS, i, j); + if (!(sts & CMD_STATUS_ISSUED) || + ((req->wait_for_compl || cmd->wait) && + !(sts & CMD_STATUS_COMPL))) { + pr_err("Incomplete request: %s: addr=%#x data=%#x", + drv->name, cmd->addr, cmd->data); + } + } +skip: + /* Reclaim the TCS */ + write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, i, 0); + write_tcs_reg(drv, RSC_DRV_IRQ_CLEAR, 0, BIT(i)); + spin_lock(&drv->lock); + clear_bit(i, drv->tcs_in_use); + spin_unlock(&drv->lock); + } + + return IRQ_HANDLED; +} + +static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id, + const struct tcs_request *msg) +{ + u32 msgid, cmd_msgid; + u32 cmd_enable = 0; + u32 cmd_complete; + struct tcs_cmd *cmd; + int i, j; + + cmd_msgid = CMD_MSGID_LEN; + cmd_msgid |= msg->wait_for_compl ? CMD_MSGID_RESP_REQ : 0; + cmd_msgid |= CMD_MSGID_WRITE; + + cmd_complete = read_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, tcs_id, 0); + + for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) { + cmd = &msg->cmds[i]; + cmd_enable |= BIT(j); + cmd_complete |= cmd->wait << j; + msgid = cmd_msgid; + msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0; + write_tcs_cmd(drv, RSC_DRV_CMD_MSGID, tcs_id, j, msgid); + write_tcs_cmd(drv, RSC_DRV_CMD_ADDR, tcs_id, j, cmd->addr); + write_tcs_cmd(drv, RSC_DRV_CMD_DATA, tcs_id, j, cmd->data); + } + + write_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, tcs_id, cmd_complete); + cmd_enable |= read_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, 0); + write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, cmd_enable); +} + +static void __tcs_trigger(struct rsc_drv *drv, int tcs_id) +{ + u32 enable; + + /* + * HW req: Clear the DRV_CONTROL and enable TCS again + * While clearing ensure that the AMC mode trigger is cleared + * and then the mode enable is cleared. + */ + enable = read_tcs_reg(drv, RSC_DRV_CONTROL, tcs_id, 0); + enable &= ~TCS_AMC_MODE_TRIGGER; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable); + enable &= ~TCS_AMC_MODE_ENABLE; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable); + + /* Enable the AMC mode on the TCS and then trigger the TCS */ + enable = TCS_AMC_MODE_ENABLE; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable); + enable |= TCS_AMC_MODE_TRIGGER; + write_tcs_reg_sync(drv, RSC_DRV_CONTROL, tcs_id, enable); +} + +static int check_for_req_inflight(struct rsc_drv *drv, struct tcs_group *tcs, + const struct tcs_request *msg) +{ + unsigned long curr_enabled; + u32 addr; + int i, j, k; + int tcs_id = tcs->offset; + + for (i = 0; i < tcs->num_tcs; i++, tcs_id++) { + if (tcs_is_free(drv, tcs_id)) + continue; + + curr_enabled = read_tcs_reg(drv, RSC_DRV_CMD_ENABLE, tcs_id, 0); + + for_each_set_bit(j, &curr_enabled, MAX_CMDS_PER_TCS) { + addr = read_tcs_reg(drv, RSC_DRV_CMD_ADDR, tcs_id, j); + for (k = 0; k < msg->num_cmds; k++) { + if (addr == msg->cmds[k].addr) + return -EBUSY; + } + } + } + + return 0; +} + +static int find_free_tcs(struct tcs_group *tcs) +{ + int i; + + for (i = 0; i < tcs->num_tcs; i++) { + if (tcs_is_free(tcs->drv, tcs->offset + i)) + return tcs->offset + i; + } + + return -EBUSY; +} + +static int tcs_write(struct rsc_drv *drv, const struct tcs_request *msg) +{ + struct tcs_group *tcs; + int tcs_id; + unsigned long flags; + int ret; + + tcs = get_tcs_for_msg(drv, msg); + if (IS_ERR(tcs)) + return PTR_ERR(tcs); + + spin_lock_irqsave(&tcs->lock, flags); + spin_lock(&drv->lock); + /* + * The h/w does not like if we send a request to the same address, + * when one is already in-flight or being processed. + */ + ret = check_for_req_inflight(drv, tcs, msg); + if (ret) { + spin_unlock(&drv->lock); + goto done_write; + } + + tcs_id = find_free_tcs(tcs); + if (tcs_id < 0) { + ret = tcs_id; + spin_unlock(&drv->lock); + goto done_write; + } + + tcs->req[tcs_id - tcs->offset] = msg; + set_bit(tcs_id, drv->tcs_in_use); + spin_unlock(&drv->lock); + + __tcs_buffer_write(drv, tcs_id, 0, msg); + __tcs_trigger(drv, tcs_id); + +done_write: + spin_unlock_irqrestore(&tcs->lock, flags); + return ret; +} + +/** + * rpmh_rsc_send_data: Validate the incoming message and write to the + * appropriate TCS block. + * + * @drv: the controller + * @msg: the data to be sent + * + * Return: 0 on success, -EINVAL on error. + * Note: This call blocks until a valid data is written to the TCS. + */ +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg) +{ + int ret; + + if (!msg || !msg->cmds || !msg->num_cmds || + msg->num_cmds > MAX_RPMH_PAYLOAD) { + WARN_ON(1); + return -EINVAL; + } + + do { + ret = tcs_write(drv, msg); + if (ret == -EBUSY) { + pr_info_ratelimited("TCS Busy, retrying RPMH message send: addr=%#x\n", + msg->cmds[0].addr); + udelay(10); + } + } while (ret == -EBUSY); + + return ret; +} + +static int rpmh_probe_tcs_config(struct platform_device *pdev, + struct rsc_drv *drv) +{ + struct tcs_type_config { + u32 type; + u32 n; + } tcs_cfg[TCS_TYPE_NR] = { { 0 } }; + struct device_node *dn = pdev->dev.of_node; + u32 config, max_tcs, ncpt, offset; + int i, ret, n, st = 0; + struct tcs_group *tcs; + struct resource *res; + void __iomem *base; + char drv_id[10] = {0}; + + snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, drv_id); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + ret = of_property_read_u32(dn, "qcom,tcs-offset", &offset); + if (ret) + return ret; + drv->tcs_base = base + offset; + + config = readl_relaxed(base + DRV_PRNT_CHLD_CONFIG); + + max_tcs = config; + max_tcs &= DRV_NUM_TCS_MASK << (DRV_NUM_TCS_SHIFT * drv->id); + max_tcs = max_tcs >> (DRV_NUM_TCS_SHIFT * drv->id); + + ncpt = config & (DRV_NCPT_MASK << DRV_NCPT_SHIFT); + ncpt = ncpt >> DRV_NCPT_SHIFT; + + n = of_property_count_u32_elems(dn, "qcom,tcs-config"); + if (n != 2 * TCS_TYPE_NR) + return -EINVAL; + + for (i = 0; i < TCS_TYPE_NR; i++) { + ret = of_property_read_u32_index(dn, "qcom,tcs-config", + i * 2, &tcs_cfg[i].type); + if (ret) + return ret; + if (tcs_cfg[i].type >= TCS_TYPE_NR) + return -EINVAL; + + ret = of_property_read_u32_index(dn, "qcom,tcs-config", + i * 2 + 1, &tcs_cfg[i].n); + if (ret) + return ret; + if (tcs_cfg[i].n > MAX_TCS_PER_TYPE) + return -EINVAL; + } + + for (i = 0; i < TCS_TYPE_NR; i++) { + tcs = &drv->tcs[tcs_cfg[i].type]; + if (tcs->drv) + return -EINVAL; + tcs->drv = drv; + tcs->type = tcs_cfg[i].type; + tcs->num_tcs = tcs_cfg[i].n; + tcs->ncpt = ncpt; + spin_lock_init(&tcs->lock); + + if (!tcs->num_tcs || tcs->type == CONTROL_TCS) + continue; + + if (st + tcs->num_tcs > max_tcs || + st + tcs->num_tcs >= BITS_PER_BYTE * sizeof(tcs->mask)) + return -EINVAL; + + tcs->mask = ((1 << tcs->num_tcs) - 1) << st; + tcs->offset = st; + st += tcs->num_tcs; + } + + drv->num_tcs = st; + + return 0; +} + +static int rpmh_rsc_probe(struct platform_device *pdev) +{ + struct device_node *dn = pdev->dev.of_node; + struct rsc_drv *drv; + int ret, irq; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + ret = of_property_read_u32(dn, "qcom,drv-id", &drv->id); + if (ret) + return ret; + + drv->name = of_get_property(dn, "label", NULL); + if (!drv->name) + drv->name = dev_name(&pdev->dev); + + ret = rpmh_probe_tcs_config(pdev, drv); + if (ret) + return ret; + + spin_lock_init(&drv->lock); + bitmap_zero(drv->tcs_in_use, MAX_TCS_NR); + + irq = platform_get_irq(pdev, drv->id); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, tcs_tx_done, + IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, + drv->name, drv); + if (ret) + return ret; + + /* Enable the active TCS to send requests immediately */ + write_tcs_reg(drv, RSC_DRV_IRQ_ENABLE, 0, drv->tcs[ACTIVE_TCS].mask); + + return devm_of_platform_populate(&pdev->dev); +} + +static const struct of_device_id rpmh_drv_match[] = { + { .compatible = "qcom,rpmh-rsc", }, + { } +}; + +static struct platform_driver rpmh_driver = { + .probe = rpmh_rsc_probe, + .driver = { + .name = "rpmh", + .of_match_table = rpmh_drv_match, + }, +}; + +static int __init rpmh_driver_init(void) +{ + return platform_driver_register(&rpmh_driver); +} +arch_initcall(rpmh_driver_init); diff --git a/include/dt-bindings/soc/qcom,rpmh-rsc.h b/include/dt-bindings/soc/qcom,rpmh-rsc.h new file mode 100644 index 000000000000..868f998ea998 --- /dev/null +++ b/include/dt-bindings/soc/qcom,rpmh-rsc.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __DT_QCOM_RPMH_RSC_H__ +#define __DT_QCOM_RPMH_RSC_H__ + +#define SLEEP_TCS 0 +#define WAKE_TCS 1 +#define ACTIVE_TCS 2 +#define CONTROL_TCS 3 + +#endif /* __DT_QCOM_RPMH_RSC_H__ */ diff --git a/include/soc/qcom/tcs.h b/include/soc/qcom/tcs.h new file mode 100644 index 000000000000..262876a59e86 --- /dev/null +++ b/include/soc/qcom/tcs.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_QCOM_TCS_H__ +#define __SOC_QCOM_TCS_H__ + +#define MAX_RPMH_PAYLOAD 16 + +/** + * rpmh_state: state for the request + * + * RPMH_SLEEP_STATE: State of the resource when the processor subsystem + * is powered down. There is no client using the + * resource actively. + * RPMH_WAKE_ONLY_STATE: Resume resource state to the value previously + * requested before the processor was powered down. + * RPMH_ACTIVE_ONLY_STATE: Active or AMC mode requests. Resource state + * is aggregated immediately. + */ +enum rpmh_state { + RPMH_SLEEP_STATE, + RPMH_WAKE_ONLY_STATE, + RPMH_ACTIVE_ONLY_STATE, +}; + +/** + * struct tcs_cmd: an individual request to RPMH. + * + * @addr: the address of the resource slv_id:18:16 | offset:0:15 + * @data: the resource state request + * @wait: wait for this request to be complete before sending the next + */ +struct tcs_cmd { + u32 addr; + u32 data; + u32 wait; +}; + +/** + * struct tcs_request: A set of tcs_cmds sent together in a TCS + * + * @state: state for the request. + * @wait_for_compl: wait until we get a response from the h/w accelerator + * @num_cmds: the number of @cmds in this request + * @cmds: an array of tcs_cmds + */ +struct tcs_request { + enum rpmh_state state; + u32 wait_for_compl; + u32 num_cmds; + struct tcs_cmd *cmds; +}; + +#endif /* __SOC_QCOM_TCS_H__ */ -- cgit v1.2.3 From fc087fe5a45e7210be89fe015505e1bb3746395c Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:57:00 +0530 Subject: drivers: qcom: rpmh-rsc: log RPMH requests in FTRACE Log sent RPMH requests and interrupt responses in FTRACE. Signed-off-by: Lina Iyer Reviewed-by: Steven Rostedt (VMware) [rplsssn@codeaurora.org: rebase to v4.18-rc1 & fix merge conflict] Signed-off-by: Raju P.L.S.S.S.N Signed-off-by: Andy Gross --- drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/rpmh-rsc.c | 11 +++++- drivers/soc/qcom/trace-rpmh.h | 82 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 drivers/soc/qcom/trace-rpmh.h (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index b2faf9aeed3f..036ecc45cbbd 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +CFLAGS_rpmh-rsc.o := -I$(src) obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index c5e0793cdfb7..89d41cdd8d71 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -23,6 +23,9 @@ #include "rpmh-internal.h" +#define CREATE_TRACE_POINTS +#include "trace-rpmh.h" + #define RSC_DRV_TCS_OFFSET 672 #define RSC_DRV_CMD_OFFSET 20 @@ -135,7 +138,7 @@ static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv, static irqreturn_t tcs_tx_done(int irq, void *p) { struct rsc_drv *drv = p; - int i, j; + int i, j, err; unsigned long irq_status; const struct tcs_request *req; struct tcs_cmd *cmd; @@ -149,6 +152,7 @@ static irqreturn_t tcs_tx_done(int irq, void *p) goto skip; } + err = 0; for (j = 0; j < req->num_cmds; j++) { u32 sts; @@ -159,8 +163,11 @@ static irqreturn_t tcs_tx_done(int irq, void *p) !(sts & CMD_STATUS_COMPL))) { pr_err("Incomplete request: %s: addr=%#x data=%#x", drv->name, cmd->addr, cmd->data); + err = -EIO; } } + + trace_rpmh_tx_done(drv, i, req, err); skip: /* Reclaim the TCS */ write_tcs_reg(drv, RSC_DRV_CMD_ENABLE, i, 0); @@ -194,9 +201,11 @@ static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id, cmd_complete |= cmd->wait << j; msgid = cmd_msgid; msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0; + write_tcs_cmd(drv, RSC_DRV_CMD_MSGID, tcs_id, j, msgid); write_tcs_cmd(drv, RSC_DRV_CMD_ADDR, tcs_id, j, cmd->addr); write_tcs_cmd(drv, RSC_DRV_CMD_DATA, tcs_id, j, cmd->data); + trace_rpmh_send_msg(drv, tcs_id, j, msgid, cmd); } write_tcs_reg(drv, RSC_DRV_CMD_WAIT_FOR_CMPL, tcs_id, cmd_complete); diff --git a/drivers/soc/qcom/trace-rpmh.h b/drivers/soc/qcom/trace-rpmh.h new file mode 100644 index 000000000000..feb0cb455e37 --- /dev/null +++ b/drivers/soc/qcom/trace-rpmh.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#if !defined(_TRACE_RPMH_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_RPMH_H + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM rpmh + +#include +#include "rpmh-internal.h" + +TRACE_EVENT(rpmh_tx_done, + + TP_PROTO(struct rsc_drv *d, int m, const struct tcs_request *r, int e), + + TP_ARGS(d, m, r, e), + + TP_STRUCT__entry( + __string(name, d->name) + __field(int, m) + __field(u32, addr) + __field(u32, data) + __field(int, err) + ), + + TP_fast_assign( + __assign_str(name, d->name); + __entry->m = m; + __entry->addr = r->cmds[0].addr; + __entry->data = r->cmds[0].data; + __entry->err = e; + ), + + TP_printk("%s: ack: tcs-m: %d addr: %#x data: %#x errno: %d", + __get_str(name), __entry->m, __entry->addr, __entry->data, + __entry->err) +); + +TRACE_EVENT(rpmh_send_msg, + + TP_PROTO(struct rsc_drv *d, int m, int n, u32 h, + const struct tcs_cmd *c), + + TP_ARGS(d, m, n, h, c), + + TP_STRUCT__entry( + __string(name, d->name) + __field(int, m) + __field(int, n) + __field(u32, hdr) + __field(u32, addr) + __field(u32, data) + __field(bool, wait) + ), + + TP_fast_assign( + __assign_str(name, d->name); + __entry->m = m; + __entry->n = n; + __entry->hdr = h; + __entry->addr = c->addr; + __entry->data = c->data; + __entry->wait = c->wait; + ), + + TP_printk("%s: send-msg: tcs(m): %d cmd(n): %d msgid: %#x addr: %#x data: %#x complete: %d", + __get_str(name), __entry->m, __entry->n, __entry->hdr, + __entry->addr, __entry->data, __entry->wait) +); + +#endif /* _TRACE_RPMH_H */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace-rpmh + +#include -- cgit v1.2.3 From c1038456b02b86cc4445441a8d33c5aca0ac103e Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:57:01 +0530 Subject: drivers: qcom: rpmh: add RPMH helper functions Sending RPMH requests and waiting for response from the controller through a callback is common functionality across all platform drivers. To simplify drivers, add a library functions to create RPMH client and send resource state requests. rpmh_write() is a synchronous blocking call that can be used to send active state requests. Signed-off-by: Lina Iyer Signed-off-by: Raju P.L.S.S.S.N Signed-off-by: Andy Gross --- drivers/soc/qcom/Makefile | 4 +- drivers/soc/qcom/rpmh-internal.h | 31 ++++++++++- drivers/soc/qcom/rpmh-rsc.c | 6 +- drivers/soc/qcom/rpmh.c | 116 +++++++++++++++++++++++++++++++++++++++ include/soc/qcom/rpmh.h | 25 +++++++++ 5 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 drivers/soc/qcom/rpmh.c create mode 100644 include/soc/qcom/rpmh.h (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 036ecc45cbbd..f25b54cd6cf8 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -9,7 +9,9 @@ obj-$(CONFIG_QCOM_PM) += spm.o obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o qmi_helpers-y += qmi_encdec.o qmi_interface.o obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o -obj-$(CONFIG_QCOM_RPMH) += rpmh-rsc.o +obj-$(CONFIG_QCOM_RPMH) += qcom_rpmh.o +qcom_rpmh-y += rpmh-rsc.o +qcom_rpmh-y += rpmh.o obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o obj-$(CONFIG_QCOM_SMEM) += smem.o obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index cc29176f1303..1e687f719301 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -41,6 +41,32 @@ struct tcs_group { const struct tcs_request *req[MAX_TCS_PER_TYPE]; }; +/** + * struct rpmh_request: the message to be sent to rpmh-rsc + * + * @msg: the request + * @cmd: the payload that will be part of the @msg + * @completion: triggered when request is done + * @dev: the device making the request + * @err: err return from the controller + */ +struct rpmh_request { + struct tcs_request msg; + struct tcs_cmd cmd[MAX_RPMH_PAYLOAD]; + struct completion *completion; + const struct device *dev; + int err; +}; + +/** + * struct rpmh_ctrlr: our representation of the controller + * + * @drv: the controller instance + */ +struct rpmh_ctrlr { + struct rsc_drv *drv; +}; + /** * struct rsc_drv: the Direct Resource Voter (DRV) of the * Resource State Coordinator controller (RSC) @@ -52,6 +78,7 @@ struct tcs_group { * @tcs: TCS groups * @tcs_in_use: s/w state of the TCS * @lock: synchronize state of the controller + * @client: handle to the DRV's client. */ struct rsc_drv { const char *name; @@ -61,9 +88,11 @@ struct rsc_drv { struct tcs_group tcs[TCS_TYPE_NR]; DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR); spinlock_t lock; + struct rpmh_ctrlr client; }; - int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg); +void rpmh_tx_done(const struct tcs_request *msg, int r); + #endif /* __RPM_INTERNAL_H__ */ diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 89d41cdd8d71..91f8d60d17cc 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -138,7 +138,7 @@ static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv, static irqreturn_t tcs_tx_done(int irq, void *p) { struct rsc_drv *drv = p; - int i, j, err; + int i, j, err = 0; unsigned long irq_status; const struct tcs_request *req; struct tcs_cmd *cmd; @@ -175,6 +175,8 @@ skip: spin_lock(&drv->lock); clear_bit(i, drv->tcs_in_use); spin_unlock(&drv->lock); + if (req) + rpmh_tx_done(req, err); } return IRQ_HANDLED; @@ -467,6 +469,8 @@ static int rpmh_rsc_probe(struct platform_device *pdev) /* Enable the active TCS to send requests immediately */ write_tcs_reg(drv, RSC_DRV_IRQ_ENABLE, 0, drv->tcs[ACTIVE_TCS].mask); + dev_set_drvdata(&pdev->dev, drv); + return devm_of_platform_populate(&pdev->dev); } diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c new file mode 100644 index 000000000000..5e022cba8ab5 --- /dev/null +++ b/drivers/soc/qcom/rpmh.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rpmh-internal.h" + +#define RPMH_TIMEOUT_MS msecs_to_jiffies(10000) + +#define DEFINE_RPMH_MSG_ONSTACK(dev, s, q, name) \ + struct rpmh_request name = { \ + .msg = { \ + .state = s, \ + .cmds = name.cmd, \ + .num_cmds = 0, \ + .wait_for_compl = true, \ + }, \ + .cmd = { { 0 } }, \ + .completion = q, \ + .dev = dev, \ + } + +#define ctrlr_to_drv(ctrlr) container_of(ctrlr, struct rsc_drv, client) + +static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct device *dev) +{ + struct rsc_drv *drv = dev_get_drvdata(dev->parent); + + return &drv->client; +} + +void rpmh_tx_done(const struct tcs_request *msg, int r) +{ + struct rpmh_request *rpm_msg = container_of(msg, struct rpmh_request, + msg); + struct completion *compl = rpm_msg->completion; + + rpm_msg->err = r; + + if (r) + dev_err(rpm_msg->dev, "RPMH TX fail in msg addr=%#x, err=%d\n", + rpm_msg->msg.cmds[0].addr, r); + + /* Signal the blocking thread we are done */ + if (compl) + complete(compl); +} + +/** + * __rpmh_write: send the RPMH request + * + * @dev: The device making the request + * @state: Active/Sleep request type + * @rpm_msg: The data that needs to be sent (cmds). + */ +static int __rpmh_write(const struct device *dev, enum rpmh_state state, + struct rpmh_request *rpm_msg) +{ + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + + rpm_msg->msg.state = state; + + if (state != RPMH_ACTIVE_ONLY_STATE) + return -EINVAL; + + WARN_ON(irqs_disabled()); + + return rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg); +} + +/** + * rpmh_write: Write a set of RPMH commands and block until response + * + * @rc: The RPMH handle got from rpmh_get_client + * @state: Active/sleep set + * @cmd: The payload data + * @n: The number of elements in @cmd + * + * May sleep. Do not call from atomic contexts. + */ +int rpmh_write(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + DECLARE_COMPLETION_ONSTACK(compl); + DEFINE_RPMH_MSG_ONSTACK(dev, state, &compl, rpm_msg); + int ret; + + if (!cmd || !n || n > MAX_RPMH_PAYLOAD) + return -EINVAL; + + memcpy(rpm_msg.cmd, cmd, n * sizeof(*cmd)); + rpm_msg.msg.num_cmds = n; + + ret = __rpmh_write(dev, state, &rpm_msg); + if (ret) + return ret; + + ret = wait_for_completion_timeout(&compl, RPMH_TIMEOUT_MS); + WARN_ON(!ret); + return (ret > 0) ? 0 : -ETIMEDOUT; +} +EXPORT_SYMBOL(rpmh_write); diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h new file mode 100644 index 000000000000..c1d0f902bd71 --- /dev/null +++ b/include/soc/qcom/rpmh.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_QCOM_RPMH_H__ +#define __SOC_QCOM_RPMH_H__ + +#include +#include + + +#if IS_ENABLED(CONFIG_QCOM_RPMH) +int rpmh_write(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n); + +#else + +static inline int rpmh_write(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ return -ENODEV; } + +#endif /* CONFIG_QCOM_RPMH */ + +#endif /* __SOC_QCOM_RPMH_H__ */ -- cgit v1.2.3 From fa460e453a83af0054dc3d3d210a6617db333a20 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:57:02 +0530 Subject: drivers: qcom: rpmh-rsc: write sleep/wake requests to TCS Sleep and wake requests are sent when the application processor subsystem of the SoC is entering deep sleep states like in suspend. These requests help lower the system power requirements when the resources are not in use. Sleep and wake requests are written to the TCS slots but are not triggered at the time of writing. The TCS are triggered by the firmware after the last of the CPUs has executed its WFI. Since these requests may come in different batches of requests, it is the job of this controller driver to find and arrange the requests into the available TCSes. Signed-off-by: Lina Iyer Signed-off-by: Raju P.L.S.S.S.N Reviewed-by: Evan Green Reviewed-by: Matthias Kaehlcke Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-internal.h | 8 +++ drivers/soc/qcom/rpmh-rsc.c | 121 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index 1e687f719301..af4da1cb6558 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -14,6 +14,7 @@ #define MAX_CMDS_PER_TCS 16 #define MAX_TCS_PER_TYPE 3 #define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR) +#define MAX_TCS_SLOTS (MAX_CMDS_PER_TCS * MAX_TCS_PER_TYPE) struct rsc_drv; @@ -29,6 +30,8 @@ struct rsc_drv; * @ncpt: number of commands in each TCS * @lock: lock for synchronizing this TCS writes * @req: requests that are sent from the TCS + * @cmd_cache: flattened cache of cmds in sleep/wake TCS + * @slots: indicates which of @cmd_addr are occupied */ struct tcs_group { struct rsc_drv *drv; @@ -39,6 +42,8 @@ struct tcs_group { int ncpt; spinlock_t lock; const struct tcs_request *req[MAX_TCS_PER_TYPE]; + u32 *cmd_cache; + DECLARE_BITMAP(slots, MAX_TCS_SLOTS); }; /** @@ -92,6 +97,9 @@ struct rsc_drv { }; int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg); +int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, + const struct tcs_request *msg); +int rpmh_rsc_invalidate(struct rsc_drv *drv); void rpmh_tx_done(const struct tcs_request *msg, int r); diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 91f8d60d17cc..604ebb6757cc 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -110,6 +110,12 @@ static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, case RPMH_ACTIVE_ONLY_STATE: type = ACTIVE_TCS; break; + case RPMH_WAKE_ONLY_STATE: + type = WAKE_TCS; + break; + case RPMH_SLEEP_STATE: + type = SLEEP_TCS; + break; default: return ERR_PTR(-EINVAL); } @@ -349,6 +355,108 @@ int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg) return ret; } +static int find_match(const struct tcs_group *tcs, const struct tcs_cmd *cmd, + int len) +{ + int i, j; + + /* Check for already cached commands */ + for_each_set_bit(i, tcs->slots, MAX_TCS_SLOTS) { + if (tcs->cmd_cache[i] != cmd[0].addr) + continue; + if (i + len >= tcs->num_tcs * tcs->ncpt) + goto seq_err; + for (j = 0; j < len; j++) { + if (tcs->cmd_cache[i + j] != cmd[j].addr) + goto seq_err; + } + return i; + } + + return -ENODATA; + +seq_err: + WARN(1, "Message does not match previous sequence.\n"); + return -EINVAL; +} + +static int find_slots(struct tcs_group *tcs, const struct tcs_request *msg, + int *tcs_id, int *cmd_id) +{ + int slot, offset; + int i = 0; + + /* Find if we already have the msg in our TCS */ + slot = find_match(tcs, msg->cmds, msg->num_cmds); + if (slot >= 0) + goto copy_data; + + /* Do over, until we can fit the full payload in a TCS */ + do { + slot = bitmap_find_next_zero_area(tcs->slots, MAX_TCS_SLOTS, + i, msg->num_cmds, 0); + if (slot == tcs->num_tcs * tcs->ncpt) + return -ENOMEM; + i += tcs->ncpt; + } while (slot + msg->num_cmds - 1 >= i); + +copy_data: + bitmap_set(tcs->slots, slot, msg->num_cmds); + /* Copy the addresses of the resources over to the slots */ + for (i = 0; i < msg->num_cmds; i++) + tcs->cmd_cache[slot + i] = msg->cmds[i].addr; + + offset = slot / tcs->ncpt; + *tcs_id = offset + tcs->offset; + *cmd_id = slot % tcs->ncpt; + + return 0; +} + +static int tcs_ctrl_write(struct rsc_drv *drv, const struct tcs_request *msg) +{ + struct tcs_group *tcs; + int tcs_id = 0, cmd_id = 0; + unsigned long flags; + int ret; + + tcs = get_tcs_for_msg(drv, msg); + if (IS_ERR(tcs)) + return PTR_ERR(tcs); + + spin_lock_irqsave(&tcs->lock, flags); + /* find the TCS id and the command in the TCS to write to */ + ret = find_slots(tcs, msg, &tcs_id, &cmd_id); + if (!ret) + __tcs_buffer_write(drv, tcs_id, cmd_id, msg); + spin_unlock_irqrestore(&tcs->lock, flags); + + return ret; +} + +/** + * rpmh_rsc_write_ctrl_data: Write request to the controller + * + * @drv: the controller + * @msg: the data to be written to the controller + * + * There is no response returned for writing the request to the controller. + */ +int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, const struct tcs_request *msg) +{ + if (!msg || !msg->cmds || !msg->num_cmds || + msg->num_cmds > MAX_RPMH_PAYLOAD) { + pr_err("Payload error\n"); + return -EINVAL; + } + + /* Data sent to this API will not be sent immediately */ + if (msg->state == RPMH_ACTIVE_ONLY_STATE) + return -EINVAL; + + return tcs_ctrl_write(drv, msg); +} + static int rpmh_probe_tcs_config(struct platform_device *pdev, struct rsc_drv *drv) { @@ -424,6 +532,19 @@ static int rpmh_probe_tcs_config(struct platform_device *pdev, tcs->mask = ((1 << tcs->num_tcs) - 1) << st; tcs->offset = st; st += tcs->num_tcs; + + /* + * Allocate memory to cache sleep and wake requests to + * avoid reading TCS register memory. + */ + if (tcs->type == ACTIVE_TCS) + continue; + + tcs->cmd_cache = devm_kcalloc(&pdev->dev, + tcs->num_tcs * ncpt, sizeof(u32), + GFP_KERNEL); + if (!tcs->cmd_cache) + return -ENOMEM; } drv->num_tcs = st; -- cgit v1.2.3 From 9a3afcfbc0cc9af5e18ff53bb0d41a30e0d3d896 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:57:03 +0530 Subject: drivers: qcom: rpmh-rsc: allow invalidation of sleep/wake TCS Allow sleep and wake commands to be cleared from the respective TCSes, so that they can be re-populated. Signed-off-by: Lina Iyer Signed-off-by: Raju P.L.S.S.S.N Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-rsc.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 604ebb6757cc..fed8459c0ef6 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -101,6 +101,50 @@ static struct tcs_group *get_tcs_of_type(struct rsc_drv *drv, int type) return &drv->tcs[type]; } +static int tcs_invalidate(struct rsc_drv *drv, int type) +{ + int m; + struct tcs_group *tcs; + + tcs = get_tcs_of_type(drv, type); + if (IS_ERR(tcs)) + return PTR_ERR(tcs); + + spin_lock(&tcs->lock); + if (bitmap_empty(tcs->slots, MAX_TCS_SLOTS)) { + spin_unlock(&tcs->lock); + return 0; + } + + for (m = tcs->offset; m < tcs->offset + tcs->num_tcs; m++) { + if (!tcs_is_free(drv, m)) { + spin_unlock(&tcs->lock); + return -EAGAIN; + } + write_tcs_reg_sync(drv, RSC_DRV_CMD_ENABLE, m, 0); + } + bitmap_zero(tcs->slots, MAX_TCS_SLOTS); + spin_unlock(&tcs->lock); + + return 0; +} + +/** + * rpmh_rsc_invalidate - Invalidate sleep and wake TCSes + * + * @drv: the RSC controller + */ +int rpmh_rsc_invalidate(struct rsc_drv *drv) +{ + int ret; + + ret = tcs_invalidate(drv, SLEEP_TCS); + if (!ret) + ret = tcs_invalidate(drv, WAKE_TCS); + + return ret; +} + static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, const struct tcs_request *msg) { -- cgit v1.2.3 From 600513dfeef33cb05c694d1b13d319b9e8cde536 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:57:04 +0530 Subject: drivers: qcom: rpmh: cache sleep/wake state requests Active state requests are sent immediately to the RSC controller, while sleep and wake state requests are cached in this driver to avoid taxing the RSC controller repeatedly. The cached values will be sent to the controller when the rpmh_flush() is called. Generally, flushing is a system PM activity and may be called from the system PM drivers when the system is entering suspend or deeper sleep modes during cpuidle. Also allow invalidating the cached requests, so they may be re-populated again. Signed-off-by: Lina Iyer [rplsssn: remove unneeded semicolon, address line over 80chars error] Signed-off-by: Raju P.L.S.S.S.N Reviewed-by: Evan Green Reviewed-by: Matthias Kaehlcke Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-internal.h | 8 +- drivers/soc/qcom/rpmh-rsc.c | 3 + drivers/soc/qcom/rpmh.c | 201 ++++++++++++++++++++++++++++++++++++++- include/soc/qcom/rpmh.h | 11 +++ 4 files changed, 216 insertions(+), 7 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index af4da1cb6558..101145c9db1c 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -66,10 +66,14 @@ struct rpmh_request { /** * struct rpmh_ctrlr: our representation of the controller * - * @drv: the controller instance + * @cache: the list of cached requests + * @cache_lock: synchronize access to the cache data + * @dirty: was the cache updated since flush */ struct rpmh_ctrlr { - struct rsc_drv *drv; + struct list_head cache; + spinlock_t cache_lock; + bool dirty; }; /** diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index fed8459c0ef6..5d0dd0581904 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -634,6 +634,9 @@ static int rpmh_rsc_probe(struct platform_device *pdev) /* Enable the active TCS to send requests immediately */ write_tcs_reg(drv, RSC_DRV_IRQ_ENABLE, 0, drv->tcs[ACTIVE_TCS].mask); + spin_lock_init(&drv->client.cache_lock); + INIT_LIST_HEAD(&drv->client.cache); + dev_set_drvdata(&pdev->dev, drv); return devm_of_platform_populate(&pdev->dev); diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 5e022cba8ab5..c355ba13e73f 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -8,10 +8,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -36,6 +38,21 @@ #define ctrlr_to_drv(ctrlr) container_of(ctrlr, struct rsc_drv, client) +/** + * struct cache_req: the request object for caching + * + * @addr: the address of the resource + * @sleep_val: the sleep vote + * @wake_val: the wake vote + * @list: linked list obj + */ +struct cache_req { + u32 addr; + u32 sleep_val; + u32 wake_val; + struct list_head list; +}; + static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct device *dev) { struct rsc_drv *drv = dev_get_drvdata(dev->parent); @@ -60,26 +77,107 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) complete(compl); } +static struct cache_req *__find_req(struct rpmh_ctrlr *ctrlr, u32 addr) +{ + struct cache_req *p, *req = NULL; + + list_for_each_entry(p, &ctrlr->cache, list) { + if (p->addr == addr) { + req = p; + break; + } + } + + return req; +} + +static struct cache_req *cache_rpm_request(struct rpmh_ctrlr *ctrlr, + enum rpmh_state state, + struct tcs_cmd *cmd) +{ + struct cache_req *req; + unsigned long flags; + + spin_lock_irqsave(&ctrlr->cache_lock, flags); + req = __find_req(ctrlr, cmd->addr); + if (req) + goto existing; + + req = kzalloc(sizeof(*req), GFP_ATOMIC); + if (!req) { + req = ERR_PTR(-ENOMEM); + goto unlock; + } + + req->addr = cmd->addr; + req->sleep_val = req->wake_val = UINT_MAX; + INIT_LIST_HEAD(&req->list); + list_add_tail(&req->list, &ctrlr->cache); + +existing: + switch (state) { + case RPMH_ACTIVE_ONLY_STATE: + if (req->sleep_val != UINT_MAX) + req->wake_val = cmd->data; + break; + case RPMH_WAKE_ONLY_STATE: + req->wake_val = cmd->data; + break; + case RPMH_SLEEP_STATE: + req->sleep_val = cmd->data; + break; + default: + break; + } + + ctrlr->dirty = true; +unlock: + spin_unlock_irqrestore(&ctrlr->cache_lock, flags); + + return req; +} + /** - * __rpmh_write: send the RPMH request + * __rpmh_write: Cache and send the RPMH request * * @dev: The device making the request * @state: Active/Sleep request type * @rpm_msg: The data that needs to be sent (cmds). + * + * Cache the RPMH request and send if the state is ACTIVE_ONLY. + * SLEEP/WAKE_ONLY requests are not sent to the controller at + * this time. Use rpmh_flush() to send them to the controller. */ static int __rpmh_write(const struct device *dev, enum rpmh_state state, struct rpmh_request *rpm_msg) { struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + int ret = -EINVAL; + struct cache_req *req; + int i; rpm_msg->msg.state = state; - if (state != RPMH_ACTIVE_ONLY_STATE) - return -EINVAL; + /* Cache the request in our store and link the payload */ + for (i = 0; i < rpm_msg->msg.num_cmds; i++) { + req = cache_rpm_request(ctrlr, state, &rpm_msg->msg.cmds[i]); + if (IS_ERR(req)) + return PTR_ERR(req); + } + + rpm_msg->msg.state = state; - WARN_ON(irqs_disabled()); + if (state == RPMH_ACTIVE_ONLY_STATE) { + WARN_ON(irqs_disabled()); + ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg); + } else { + ret = rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr), + &rpm_msg->msg); + /* Clean up our call by spoofing tx_done */ + rpmh_tx_done(&rpm_msg->msg, ret); + } - return rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg); + return ret; } /** @@ -114,3 +212,96 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, return (ret > 0) ? 0 : -ETIMEDOUT; } EXPORT_SYMBOL(rpmh_write); + +static int is_req_valid(struct cache_req *req) +{ + return (req->sleep_val != UINT_MAX && + req->wake_val != UINT_MAX && + req->sleep_val != req->wake_val); +} + +static int send_single(const struct device *dev, enum rpmh_state state, + u32 addr, u32 data) +{ + DEFINE_RPMH_MSG_ONSTACK(dev, state, NULL, rpm_msg); + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + + /* Wake sets are always complete and sleep sets are not */ + rpm_msg.msg.wait_for_compl = (state == RPMH_WAKE_ONLY_STATE); + rpm_msg.cmd[0].addr = addr; + rpm_msg.cmd[0].data = data; + rpm_msg.msg.num_cmds = 1; + + return rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr), &rpm_msg.msg); +} + +/** + * rpmh_flush: Flushes the buffered active and sleep sets to TCS + * + * @dev: The device making the request + * + * Return: -EBUSY if the controller is busy, probably waiting on a response + * to a RPMH request sent earlier. + * + * This function is always called from the sleep code from the last CPU + * that is powering down the entire system. Since no other RPMH API would be + * executing at this time, it is safe to run lockless. + */ +int rpmh_flush(const struct device *dev) +{ + struct cache_req *p; + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + int ret; + + if (!ctrlr->dirty) { + pr_debug("Skipping flush, TCS has latest data.\n"); + return 0; + } + + /* + * Nobody else should be calling this function other than system PM, + * hence we can run without locks. + */ + list_for_each_entry(p, &ctrlr->cache, list) { + if (!is_req_valid(p)) { + pr_debug("%s: skipping RPMH req: a:%#x s:%#x w:%#x", + __func__, p->addr, p->sleep_val, p->wake_val); + continue; + } + ret = send_single(dev, RPMH_SLEEP_STATE, p->addr, p->sleep_val); + if (ret) + return ret; + ret = send_single(dev, RPMH_WAKE_ONLY_STATE, + p->addr, p->wake_val); + if (ret) + return ret; + } + + ctrlr->dirty = false; + + return 0; +} +EXPORT_SYMBOL(rpmh_flush); + +/** + * rpmh_invalidate: Invalidate all sleep and active sets + * sets. + * + * @dev: The device making the request + * + * Invalidate the sleep and active values in the TCS blocks. + */ +int rpmh_invalidate(const struct device *dev) +{ + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + int ret; + + ctrlr->dirty = true; + + do { + ret = rpmh_rsc_invalidate(ctrlr_to_drv(ctrlr)); + } while (ret == -EAGAIN); + + return ret; +} +EXPORT_SYMBOL(rpmh_invalidate); diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h index c1d0f902bd71..42e62a0d26d8 100644 --- a/include/soc/qcom/rpmh.h +++ b/include/soc/qcom/rpmh.h @@ -14,12 +14,23 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n); +int rpmh_flush(const struct device *dev); + +int rpmh_invalidate(const struct device *dev); + #else static inline int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n) { return -ENODEV; } + +static inline int rpmh_flush(const struct device *dev) +{ return -ENODEV; } + +static inline int rpmh_invalidate(const struct device *dev) +{ return -ENODEV; } + #endif /* CONFIG_QCOM_RPMH */ #endif /* __SOC_QCOM_RPMH_H__ */ -- cgit v1.2.3 From 564b5e24ccd4c840a7f84dfd952e5715dd9b3966 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:57:05 +0530 Subject: drivers: qcom: rpmh: allow requests to be sent asynchronously Platform drivers that want to send a request but do not want to block until the RPMH request completes have now a new API - rpmh_write_async(). The API allocates memory and send the requests and returns the control back to the platform driver. The tx_done callback from the controller is handled in the context of the controller's thread and frees the allocated memory. This API allows RPMH requests from atomic contexts as well. Signed-off-by: Lina Iyer Signed-off-by: Raju P.L.S.S.S.N Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-internal.h | 2 ++ drivers/soc/qcom/rpmh.c | 51 ++++++++++++++++++++++++++++++++++++++++ include/soc/qcom/rpmh.h | 7 ++++++ 3 files changed, 60 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index 101145c9db1c..6a8a4b7aeead 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -54,6 +54,7 @@ struct tcs_group { * @completion: triggered when request is done * @dev: the device making the request * @err: err return from the controller + * @needs_free: check to free dynamically allocated request object */ struct rpmh_request { struct tcs_request msg; @@ -61,6 +62,7 @@ struct rpmh_request { struct completion *completion; const struct device *dev; int err; + bool needs_free; }; /** diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index c355ba13e73f..1e3d34876af1 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -34,6 +34,7 @@ .cmd = { { 0 } }, \ .completion = q, \ .dev = dev, \ + .needs_free = false, \ } #define ctrlr_to_drv(ctrlr) container_of(ctrlr, struct rsc_drv, client) @@ -75,6 +76,9 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) /* Signal the blocking thread we are done */ if (compl) complete(compl); + + if (rpm_msg->needs_free) + kfree(rpm_msg); } static struct cache_req *__find_req(struct rpmh_ctrlr *ctrlr, u32 addr) @@ -180,6 +184,53 @@ static int __rpmh_write(const struct device *dev, enum rpmh_state state, return ret; } +static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + if (!cmd || !n || n > MAX_RPMH_PAYLOAD) + return -EINVAL; + + memcpy(req->cmd, cmd, n * sizeof(*cmd)); + + req->msg.state = state; + req->msg.cmds = req->cmd; + req->msg.num_cmds = n; + + return 0; +} + +/** + * rpmh_write_async: Write a set of RPMH commands + * + * @dev: The device making the request + * @state: Active/sleep set + * @cmd: The payload data + * @n: The number of elements in payload + * + * Write a set of RPMH commands, the order of commands is maintained + * and will be sent as a single shot. + */ +int rpmh_write_async(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + struct rpmh_request *rpm_msg; + int ret; + + rpm_msg = kzalloc(sizeof(*rpm_msg), GFP_ATOMIC); + if (!rpm_msg) + return -ENOMEM; + rpm_msg->needs_free = true; + + ret = __fill_rpmh_msg(rpm_msg, state, cmd, n); + if (ret) { + kfree(rpm_msg); + return ret; + } + + return __rpmh_write(dev, state, rpm_msg); +} +EXPORT_SYMBOL(rpmh_write_async); + /** * rpmh_write: Write a set of RPMH commands and block until response * diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h index 42e62a0d26d8..1161a5c77e75 100644 --- a/include/soc/qcom/rpmh.h +++ b/include/soc/qcom/rpmh.h @@ -14,6 +14,9 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n); +int rpmh_write_async(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n); + int rpmh_flush(const struct device *dev); int rpmh_invalidate(const struct device *dev); @@ -24,6 +27,10 @@ static inline int rpmh_write(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n) { return -ENODEV; } +static inline int rpmh_write_async(const struct device *dev, + enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ return -ENODEV; } static inline int rpmh_flush(const struct device *dev) { return -ENODEV; } -- cgit v1.2.3 From c8790cb6da58d3fa09dfa707aa486fe6769c23bc Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:57:06 +0530 Subject: drivers: qcom: rpmh: add support for batch RPMH request Platform drivers need make a lot of resource state requests at the same time, say, at the start or end of an usecase. It can be quite inefficient to send each request separately. Instead they can give the RPMH library a batch of requests to be sent and wait on the whole transaction to be complete. rpmh_write_batch() is a blocking call that can be used to send multiple RPMH command sets. Each RPMH command set is set asynchronously and the API blocks until all the command sets are complete and receive their tx_done callbacks. Signed-off-by: Lina Iyer Signed-off-by: Raju P.L.S.S.S.N Reviewed-by: Matthias Kaehlcke Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-internal.h | 2 + drivers/soc/qcom/rpmh-rsc.c | 1 + drivers/soc/qcom/rpmh.c | 159 ++++++++++++++++++++++++++++++++++++++- include/soc/qcom/rpmh.h | 8 ++ 4 files changed, 168 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index 6a8a4b7aeead..a7bbbb67991c 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -71,11 +71,13 @@ struct rpmh_request { * @cache: the list of cached requests * @cache_lock: synchronize access to the cache data * @dirty: was the cache updated since flush + * @batch_cache: Cache sleep and wake requests sent as batch */ struct rpmh_ctrlr { struct list_head cache; spinlock_t cache_lock; bool dirty; + struct list_head batch_cache; }; /** diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 5d0dd0581904..4c0c1f24911a 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -636,6 +636,7 @@ static int rpmh_rsc_probe(struct platform_device *pdev) spin_lock_init(&drv->client.cache_lock); INIT_LIST_HEAD(&drv->client.cache); + INIT_LIST_HEAD(&drv->client.batch_cache); dev_set_drvdata(&pdev->dev, drv); diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 1e3d34876af1..c7beb6841289 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -54,6 +54,20 @@ struct cache_req { struct list_head list; }; +/** + * struct batch_cache_req - An entry in our batch catch + * + * @list: linked list obj + * @count: number of messages + * @rpm_msgs: the messages + */ + +struct batch_cache_req { + struct list_head list; + int count; + struct rpmh_request rpm_msgs[]; +}; + static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct device *dev) { struct rsc_drv *drv = dev_get_drvdata(dev->parent); @@ -73,10 +87,13 @@ void rpmh_tx_done(const struct tcs_request *msg, int r) dev_err(rpm_msg->dev, "RPMH TX fail in msg addr=%#x, err=%d\n", rpm_msg->msg.cmds[0].addr, r); + if (!compl) + goto exit; + /* Signal the blocking thread we are done */ - if (compl) - complete(compl); + complete(compl); +exit: if (rpm_msg->needs_free) kfree(rpm_msg); } @@ -264,6 +281,138 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, } EXPORT_SYMBOL(rpmh_write); +static void cache_batch(struct rpmh_ctrlr *ctrlr, struct batch_cache_req *req) +{ + unsigned long flags; + + spin_lock_irqsave(&ctrlr->cache_lock, flags); + list_add_tail(&req->list, &ctrlr->batch_cache); + spin_unlock_irqrestore(&ctrlr->cache_lock, flags); +} + +static int flush_batch(struct rpmh_ctrlr *ctrlr) +{ + struct batch_cache_req *req; + const struct rpmh_request *rpm_msg; + unsigned long flags; + int ret = 0; + int i; + + /* Send Sleep/Wake requests to the controller, expect no response */ + spin_lock_irqsave(&ctrlr->cache_lock, flags); + list_for_each_entry(req, &ctrlr->batch_cache, list) { + for (i = 0; i < req->count; i++) { + rpm_msg = req->rpm_msgs + i; + ret = rpmh_rsc_write_ctrl_data(ctrlr_to_drv(ctrlr), + &rpm_msg->msg); + if (ret) + break; + } + } + spin_unlock_irqrestore(&ctrlr->cache_lock, flags); + + return ret; +} + +static void invalidate_batch(struct rpmh_ctrlr *ctrlr) +{ + struct batch_cache_req *req, *tmp; + unsigned long flags; + + spin_lock_irqsave(&ctrlr->cache_lock, flags); + list_for_each_entry_safe(req, tmp, &ctrlr->batch_cache, list) + kfree(req); + INIT_LIST_HEAD(&ctrlr->batch_cache); + spin_unlock_irqrestore(&ctrlr->cache_lock, flags); +} + +/** + * rpmh_write_batch: Write multiple sets of RPMH commands and wait for the + * batch to finish. + * + * @dev: the device making the request + * @state: Active/sleep set + * @cmd: The payload data + * @n: The array of count of elements in each batch, 0 terminated. + * + * Write a request to the RSC controller without caching. If the request + * state is ACTIVE, then the requests are treated as completion request + * and sent to the controller immediately. The function waits until all the + * commands are complete. If the request was to SLEEP or WAKE_ONLY, then the + * request is sent as fire-n-forget and no ack is expected. + * + * May sleep. Do not call from atomic contexts for ACTIVE_ONLY requests. + */ +int rpmh_write_batch(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 *n) +{ + struct batch_cache_req *req; + struct rpmh_request *rpm_msgs; + DECLARE_COMPLETION_ONSTACK(compl); + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + unsigned long time_left; + int count = 0; + int ret, i, j; + + if (!cmd || !n) + return -EINVAL; + + while (n[count] > 0) + count++; + if (!count) + return -EINVAL; + + req = kzalloc(sizeof(*req) + count * sizeof(req->rpm_msgs[0]), + GFP_ATOMIC); + if (!req) + return -ENOMEM; + req->count = count; + rpm_msgs = req->rpm_msgs; + + for (i = 0; i < count; i++) { + __fill_rpmh_msg(rpm_msgs + i, state, cmd, n[i]); + cmd += n[i]; + } + + if (state != RPMH_ACTIVE_ONLY_STATE) { + cache_batch(ctrlr, req); + return 0; + } + + for (i = 0; i < count; i++) { + rpm_msgs[i].completion = &compl; + ret = rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msgs[i].msg); + if (ret) { + pr_err("Error(%d) sending RPMH message addr=%#x\n", + ret, rpm_msgs[i].msg.cmds[0].addr); + for (j = i; j < count; j++) + rpmh_tx_done(&rpm_msgs[j].msg, ret); + break; + } + } + + time_left = RPMH_TIMEOUT_MS; + for (i = 0; i < count; i++) { + time_left = wait_for_completion_timeout(&compl, time_left); + if (!time_left) { + /* + * Better hope they never finish because they'll signal + * the completion on our stack and that's bad once + * we've returned from the function. + */ + WARN_ON(1); + ret = -ETIMEDOUT; + goto exit; + } + } + +exit: + kfree(req); + + return ret; +} +EXPORT_SYMBOL(rpmh_write_batch); + static int is_req_valid(struct cache_req *req) { return (req->sleep_val != UINT_MAX && @@ -309,6 +458,11 @@ int rpmh_flush(const struct device *dev) return 0; } + /* First flush the cached batch requests */ + ret = flush_batch(ctrlr); + if (ret) + return ret; + /* * Nobody else should be calling this function other than system PM, * hence we can run without locks. @@ -347,6 +501,7 @@ int rpmh_invalidate(const struct device *dev) struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); int ret; + invalidate_batch(ctrlr); ctrlr->dirty = true; do { diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h index 1161a5c77e75..619e07c75da9 100644 --- a/include/soc/qcom/rpmh.h +++ b/include/soc/qcom/rpmh.h @@ -17,6 +17,9 @@ int rpmh_write(const struct device *dev, enum rpmh_state state, int rpmh_write_async(const struct device *dev, enum rpmh_state state, const struct tcs_cmd *cmd, u32 n); +int rpmh_write_batch(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 *n); + int rpmh_flush(const struct device *dev); int rpmh_invalidate(const struct device *dev); @@ -32,6 +35,11 @@ static inline int rpmh_write_async(const struct device *dev, const struct tcs_cmd *cmd, u32 n) { return -ENODEV; } +static inline int rpmh_write_batch(const struct device *dev, + enum rpmh_state state, + const struct tcs_cmd *cmd, u32 *n) +{ return -ENODEV; } + static inline int rpmh_flush(const struct device *dev) { return -ENODEV; } -- cgit v1.2.3 From 2de4b8d33eab86a6d9653041f8543f6f5f882a25 Mon Sep 17 00:00:00 2001 From: Lina Iyer Date: Wed, 20 Jun 2018 18:57:07 +0530 Subject: drivers: qcom: rpmh-rsc: allow active requests from wake TCS Some RSCs may only have sleep and wake TCS, i.e, there is no dedicated TCS for active mode request, but drivers may still want to make active requests from these RSCs. In such cases re-purpose the wake TCS to send active state requests. The requirement for this is that the driver is aware that the wake TCS is being repurposed to send active request, hence the sleep and wake TCSes be invalidated before the active request is sent. Signed-off-by: Lina Iyer Signed-off-by: Raju P.L.S.S.S.N Reviewed-by: Matthias Kaehlcke Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-rsc.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 4c0c1f24911a..098feb928576 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -148,7 +148,8 @@ int rpmh_rsc_invalidate(struct rsc_drv *drv) static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, const struct tcs_request *msg) { - int type; + int type, ret; + struct tcs_group *tcs; switch (msg->state) { case RPMH_ACTIVE_ONLY_STATE: @@ -164,7 +165,25 @@ static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, return ERR_PTR(-EINVAL); } - return get_tcs_of_type(drv, type); + /* + * If we are making an active request on a RSC that does not have a + * dedicated TCS for active state use, then re-purpose a wake TCS to + * send active votes. + * NOTE: The driver must be aware that this RSC does not have a + * dedicated AMC, and therefore would invalidate the sleep and wake + * TCSes before making an active state request. + */ + tcs = get_tcs_of_type(drv, type); + if (msg->state == RPMH_ACTIVE_ONLY_STATE && IS_ERR(tcs)) { + tcs = get_tcs_of_type(drv, WAKE_TCS); + if (!IS_ERR(tcs)) { + ret = rpmh_rsc_invalidate(drv); + if (ret) + return ERR_PTR(ret); + } + } + + return tcs; } static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv, -- cgit v1.2.3 From fdd102b52cfd9aa34b078b2b6f1a9aeafef04537 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Mon, 18 Jun 2018 11:36:52 -0700 Subject: drivers: qcom: rpmh-rsc: Check cmd_db_ready() to help children Children of RPMh will need access to cmd_db. Rather than having each child have code to check if cmd_db is ready let's add the check to RPMh. With this we'll be able to remove this boilerplate code from clk-rpmh.c and qcom-rpmh-regulator.c. Neither of these files has landed upstream yet but patches are pretty far along. === This code is based upon v11 of Lina and Raju's RPMh series. Suggested-by: Stephen Boyd Signed-off-by: Douglas Anderson Acked-by: Lina Iyer Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-rsc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 098feb928576..8e297759c162 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -621,6 +622,18 @@ static int rpmh_rsc_probe(struct platform_device *pdev) struct rsc_drv *drv; int ret, irq; + /* + * Even though RPMh doesn't directly use cmd-db, all of its children + * do. To avoid adding this check to our children we'll do it now. + */ + ret = cmd_db_ready(); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Command DB not available (%d)\n", + ret); + return ret; + } + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); if (!drv) return -ENOMEM; -- cgit v1.2.3 From efa1c257b3fc7ef2a1db604b260784cc8bcdfd23 Mon Sep 17 00:00:00 2001 From: "Raju P.L.S.S.S.N" Date: Thu, 19 Jul 2018 12:43:02 +0530 Subject: drivers: qcom: rpmh-rsc: fix the loop index check in get_req_from_tcs get_req_from_tcs introduced in patch[1] returns tcs_request from tcs_group. The size of tcs (of type - tcs_group) array in rsc_drv is TCS_TYPE_NR. So the loop index needs to be iterated up to TCS_TYPE_NR only. [1] https://patchwork.kernel.org/patch/10477547/ Signed-off-by: Raju P.L.S.S.S.N Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-rsc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 8e297759c162..8d9da6e7df0e 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -193,7 +193,7 @@ static const struct tcs_request *get_req_from_tcs(struct rsc_drv *drv, struct tcs_group *tcs; int i; - for (i = 0; i < drv->num_tcs; i++) { + for (i = 0; i < TCS_TYPE_NR; i++) { tcs = &drv->tcs[i]; if (tcs->mask & BIT(tcs_id)) return tcs->req[tcs_id - tcs->offset]; -- cgit v1.2.3 From 6c805adf17d491ea66d3cf80db3ada75b7180268 Mon Sep 17 00:00:00 2001 From: "Raju P.L.S.S.S.N" Date: Fri, 13 Jul 2018 19:15:46 +0530 Subject: drivers: qcom: rpmh: fix unwanted error check for get_tcs_of_type() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The patch fixes the bug reported by Dan Carpenter. It removes the unnecessary err check for ‘tcs’ reported by static checker warning: drivers/soc/qcom/rpmh-rsc.c:111 tcs_invalidate() warn: 'tcs' isn't an ERR_PTR See also: drivers/soc/qcom/rpmh-rsc.c:178 get_tcs_for_msg() warn: 'tcs' isn't an ERR_PTR drivers/soc/qcom/rpmh-rsc.c:180 get_tcs_for_msg() warn: 'tcs' isn't an ERR_PTR https://www.spinics.net/lists/linux-soc/msg04624.html Fixes: 9a3afcf ("drivers: qcom: rpmh-rsc: allow invalidation of sleep/wake TCS") Reported-by: Dan Carpenter Signed-off-by: Raju P.L.S.S.S.N Reviewed-by: Lina Iyer Signed-off-by: Andy Gross --- drivers/soc/qcom/rpmh-rsc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 8d9da6e7df0e..ee75da66d64b 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -108,8 +108,6 @@ static int tcs_invalidate(struct rsc_drv *drv, int type) struct tcs_group *tcs; tcs = get_tcs_of_type(drv, type); - if (IS_ERR(tcs)) - return PTR_ERR(tcs); spin_lock(&tcs->lock); if (bitmap_empty(tcs->slots, MAX_TCS_SLOTS)) { @@ -175,9 +173,9 @@ static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, * TCSes before making an active state request. */ tcs = get_tcs_of_type(drv, type); - if (msg->state == RPMH_ACTIVE_ONLY_STATE && IS_ERR(tcs)) { + if (msg->state == RPMH_ACTIVE_ONLY_STATE && !tcs->num_tcs) { tcs = get_tcs_of_type(drv, WAKE_TCS); - if (!IS_ERR(tcs)) { + if (tcs->num_tcs) { ret = rpmh_rsc_invalidate(drv); if (ret) return ERR_PTR(ret); -- cgit v1.2.3 From 4da3b0452bc66af8feca71b176668faf3d0e750d Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 29 Jun 2018 17:44:47 +0200 Subject: soc: qcom: llc-slice: Add missing MODULE_LICENSE() Add missing MODULE_LICENSE(). According to the SPDX-License-Identifier, the license is GPL v2. Fixes the following warning: WARNING: modpost: missing MODULE_LICENSE() in drivers/soc/qcom/llcc-slice.o Fixes: a3134fb ("drivers: soc: Add LLCC driver") Signed-off-by: Niklas Cassel Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/llcc-slice.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/llcc-slice.c b/drivers/soc/qcom/llcc-slice.c index fcaad1a4b41d..54063a31132f 100644 --- a/drivers/soc/qcom/llcc-slice.c +++ b/drivers/soc/qcom/llcc-slice.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -333,3 +334,5 @@ int qcom_llcc_probe(struct platform_device *pdev, return qcom_llcc_cfg_program(pdev); } EXPORT_SYMBOL_GPL(qcom_llcc_probe); + +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 78ee559d7fc65e37670a46cfbeaaa62cb014af67 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 12 Jun 2018 14:41:41 +0200 Subject: soc: qcom: rmtfs-mem: fix memleak in probe error paths Make sure to set the mem device release callback before calling put_device() in a couple of probe error paths so that the containing object also gets freed. Fixes: d1de6d6c639b ("soc: qcom: Remote filesystem memory driver") Cc: stable # 4.15 Cc: Bjorn Andersson Signed-off-by: Johan Hovold Signed-off-by: Andy Gross --- drivers/soc/qcom/rmtfs_mem.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/rmtfs_mem.c b/drivers/soc/qcom/rmtfs_mem.c index c8999e38b005..8a3678c2e83c 100644 --- a/drivers/soc/qcom/rmtfs_mem.c +++ b/drivers/soc/qcom/rmtfs_mem.c @@ -184,6 +184,7 @@ static int qcom_rmtfs_mem_probe(struct platform_device *pdev) device_initialize(&rmtfs_mem->dev); rmtfs_mem->dev.parent = &pdev->dev; rmtfs_mem->dev.groups = qcom_rmtfs_mem_groups; + rmtfs_mem->dev.release = qcom_rmtfs_mem_release_device; rmtfs_mem->base = devm_memremap(&rmtfs_mem->dev, rmtfs_mem->addr, rmtfs_mem->size, MEMREMAP_WC); @@ -206,8 +207,6 @@ static int qcom_rmtfs_mem_probe(struct platform_device *pdev) goto put_device; } - rmtfs_mem->dev.release = qcom_rmtfs_mem_release_device; - ret = of_property_read_u32(node, "qcom,vmid", &vmid); if (ret < 0 && ret != -EINVAL) { dev_err(&pdev->dev, "failed to parse qcom,vmid\n"); -- cgit v1.2.3