diff options
Diffstat (limited to 'drivers')
96 files changed, 6617 insertions, 2355 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index a4cf3d692dc3..3c68e174a113 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -30,7 +30,7 @@ config ARM_INTEGRATOR_LM found on the ARM Integrator AP (Application Platform) config BRCMSTB_GISB_ARB - bool "Broadcom STB GISB bus arbiter" + tristate "Broadcom STB GISB bus arbiter" depends on ARM || ARM64 || MIPS default ARCH_BRCMSTB || BMIPS_GENERIC help diff --git a/drivers/bus/brcmstb_gisb.c b/drivers/bus/brcmstb_gisb.c index 6551286a60cc..4c2f7d61cb9b 100644 --- a/drivers/bus/brcmstb_gisb.c +++ b/drivers/bus/brcmstb_gisb.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (C) 2014-2017 Broadcom + * Copyright (C) 2014-2021 Broadcom */ #include <linux/init.h> @@ -536,6 +536,7 @@ static struct platform_driver brcmstb_gisb_arb_driver = { .name = "brcm-gisb-arb", .of_match_table = brcmstb_gisb_arb_of_match, .pm = &brcmstb_gisb_arb_pm_ops, + .suppress_bind_attrs = true, }, }; @@ -546,3 +547,7 @@ static int __init brcm_gisb_driver_init(void) } module_init(brcm_gisb_driver_init); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom STB GISB arbiter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/bus/sun50i-de2.c b/drivers/bus/sun50i-de2.c index 672518741f86..414f29cdedf0 100644 --- a/drivers/bus/sun50i-de2.c +++ b/drivers/bus/sun50i-de2.c @@ -15,10 +15,9 @@ static int sun50i_de2_bus_probe(struct platform_device *pdev) int ret; ret = sunxi_sram_claim(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, "Error couldn't map SRAM to device\n"); - return ret; - } + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Couldn't map SRAM to device\n"); of_platform_populate(np, NULL, NULL, &pdev->dev); diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c index 6a8b7fb5be58..54c0ee6dda30 100644 --- a/drivers/bus/ti-sysc.c +++ b/drivers/bus/ti-sysc.c @@ -6,6 +6,7 @@ #include <linux/io.h> #include <linux/clk.h> #include <linux/clkdev.h> +#include <linux/cpu_pm.h> #include <linux/delay.h> #include <linux/list.h> #include <linux/module.h> @@ -17,6 +18,7 @@ #include <linux/of_platform.h> #include <linux/slab.h> #include <linux/sys_soc.h> +#include <linux/timekeeping.h> #include <linux/iopoll.h> #include <linux/platform_data/ti-sysc.h> @@ -51,11 +53,18 @@ struct sysc_address { struct list_head node; }; +struct sysc_module { + struct sysc *ddata; + struct list_head node; +}; + struct sysc_soc_info { unsigned long general_purpose:1; enum sysc_soc soc; - struct mutex list_lock; /* disabled modules list lock */ + struct mutex list_lock; /* disabled and restored modules list lock */ struct list_head disabled_modules; + struct list_head restored_modules; + struct notifier_block nb; }; enum sysc_clocks { @@ -131,6 +140,7 @@ struct sysc { struct ti_sysc_cookie cookie; const char *name; u32 revision; + u32 sysconfig; unsigned int reserved:1; unsigned int enabled:1; unsigned int needs_resume:1; @@ -147,6 +157,7 @@ struct sysc { static void sysc_parse_dts_quirks(struct sysc *ddata, struct device_node *np, bool is_child); +static int sysc_reset(struct sysc *ddata); static void sysc_write(struct sysc *ddata, int offset, u32 value) { @@ -223,37 +234,77 @@ static u32 sysc_read_sysstatus(struct sysc *ddata) return sysc_read(ddata, offset); } -/* Poll on reset status */ -static int sysc_wait_softreset(struct sysc *ddata) +static int sysc_poll_reset_sysstatus(struct sysc *ddata) { - u32 sysc_mask, syss_done, rstval; - int syss_offset, error = 0; - - if (ddata->cap->regbits->srst_shift < 0) - return 0; - - syss_offset = ddata->offsets[SYSC_SYSSTATUS]; - sysc_mask = BIT(ddata->cap->regbits->srst_shift); + int error, retries; + u32 syss_done, rstval; if (ddata->cfg.quirks & SYSS_QUIRK_RESETDONE_INVERTED) syss_done = 0; else syss_done = ddata->cfg.syss_mask; - if (syss_offset >= 0) { + if (likely(!timekeeping_suspended)) { error = readx_poll_timeout_atomic(sysc_read_sysstatus, ddata, rstval, (rstval & ddata->cfg.syss_mask) == syss_done, 100, MAX_MODULE_SOFTRESET_WAIT); + } else { + retries = MAX_MODULE_SOFTRESET_WAIT; + while (retries--) { + rstval = sysc_read_sysstatus(ddata); + if ((rstval & ddata->cfg.syss_mask) == syss_done) + return 0; + udelay(2); /* Account for udelay flakeyness */ + } + error = -ETIMEDOUT; + } + + return error; +} + +static int sysc_poll_reset_sysconfig(struct sysc *ddata) +{ + int error, retries; + u32 sysc_mask, rstval; + + sysc_mask = BIT(ddata->cap->regbits->srst_shift); - } else if (ddata->cfg.quirks & SYSC_QUIRK_RESET_STATUS) { + if (likely(!timekeeping_suspended)) { error = readx_poll_timeout_atomic(sysc_read_sysconfig, ddata, rstval, !(rstval & sysc_mask), 100, MAX_MODULE_SOFTRESET_WAIT); + } else { + retries = MAX_MODULE_SOFTRESET_WAIT; + while (retries--) { + rstval = sysc_read_sysconfig(ddata); + if (!(rstval & sysc_mask)) + return 0; + udelay(2); /* Account for udelay flakeyness */ + } + error = -ETIMEDOUT; } return error; } +/* Poll on reset status */ +static int sysc_wait_softreset(struct sysc *ddata) +{ + int syss_offset, error = 0; + + if (ddata->cap->regbits->srst_shift < 0) + return 0; + + syss_offset = ddata->offsets[SYSC_SYSSTATUS]; + + if (syss_offset >= 0) + error = sysc_poll_reset_sysstatus(ddata); + else if (ddata->cfg.quirks & SYSC_QUIRK_RESET_STATUS) + error = sysc_poll_reset_sysconfig(ddata); + + return error; +} + static int sysc_add_named_clock_from_child(struct sysc *ddata, const char *name, const char *optfck_name) @@ -1094,7 +1145,8 @@ set_midle: best_mode = fls(ddata->cfg.midlemodes) - 1; if (best_mode > SYSC_IDLE_MASK) { dev_err(dev, "%s: invalid midlemode\n", __func__); - return -EINVAL; + error = -EINVAL; + goto save_context; } if (ddata->cfg.quirks & SYSC_QUIRK_SWSUP_MSTANDBY) @@ -1112,13 +1164,16 @@ set_autoidle: sysc_write_sysconfig(ddata, reg); } - /* Flush posted write */ - sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]); + error = 0; + +save_context: + /* Save context and flush posted write */ + ddata->sysconfig = sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]); if (ddata->module_enable_quirk) ddata->module_enable_quirk(ddata); - return 0; + return error; } static int sysc_best_idle_mode(u32 idlemodes, u32 *best_mode) @@ -1175,8 +1230,10 @@ static int sysc_disable_module(struct device *dev) set_sidle: /* Set SIDLE mode */ idlemodes = ddata->cfg.sidlemodes; - if (!idlemodes || regbits->sidle_shift < 0) - return 0; + if (!idlemodes || regbits->sidle_shift < 0) { + ret = 0; + goto save_context; + } if (ddata->cfg.quirks & SYSC_QUIRK_SWSUP_SIDLE) { best_mode = SYSC_IDLE_FORCE; @@ -1184,7 +1241,8 @@ set_sidle: ret = sysc_best_idle_mode(idlemodes, &best_mode); if (ret) { dev_err(dev, "%s: invalid sidlemode\n", __func__); - return ret; + ret = -EINVAL; + goto save_context; } } @@ -1195,10 +1253,13 @@ set_sidle: reg |= 1 << regbits->autoidle_shift; sysc_write_sysconfig(ddata, reg); - /* Flush posted write */ - sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]); + ret = 0; - return 0; +save_context: + /* Save context and flush posted write */ + ddata->sysconfig = sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]); + + return ret; } static int __maybe_unused sysc_runtime_suspend_legacy(struct device *dev, @@ -1336,13 +1397,40 @@ err_allow_idle: return error; } +/* + * Checks if device context was lost. Assumes the sysconfig register value + * after lost context is different from the configured value. Only works for + * enabled devices. + * + * Eventually we may want to also add support to using the context lost + * registers that some SoCs have. + */ +static int sysc_check_context(struct sysc *ddata) +{ + u32 reg; + + if (!ddata->enabled) + return -ENODATA; + + reg = sysc_read(ddata, ddata->offsets[SYSC_SYSCONFIG]); + if (reg == ddata->sysconfig) + return 0; + + return -EACCES; +} + static int sysc_reinit_module(struct sysc *ddata, bool leave_enabled) { struct device *dev = ddata->dev; int error; - /* Disable target module if it is enabled */ if (ddata->enabled) { + /* Nothing to do if enabled and context not lost */ + error = sysc_check_context(ddata); + if (!error) + return 0; + + /* Disable target module if it is enabled */ error = sysc_runtime_suspend(dev); if (error) dev_warn(dev, "reinit suspend failed: %i\n", error); @@ -1353,6 +1441,15 @@ static int sysc_reinit_module(struct sysc *ddata, bool leave_enabled) if (error) dev_warn(dev, "reinit resume failed: %i\n", error); + /* Some modules like am335x gpmc need reset and restore of sysconfig */ + if (ddata->cfg.quirks & SYSC_QUIRK_RESET_ON_CTX_LOST) { + error = sysc_reset(ddata); + if (error) + dev_warn(dev, "reinit reset failed: %i\n", error); + + sysc_write_sysconfig(ddata, ddata->sysconfig); + } + if (leave_enabled) return error; @@ -1442,10 +1539,6 @@ struct sysc_revision_quirk { static const struct sysc_revision_quirk sysc_revision_quirks[] = { /* These drivers need to be fixed to not use pm_runtime_irq_safe() */ - SYSC_QUIRK("gpio", 0, 0, 0x10, 0x114, 0x50600801, 0xffff00ff, - SYSC_QUIRK_LEGACY_IDLE | SYSC_QUIRK_OPT_CLKS_IN_RESET), - SYSC_QUIRK("sham", 0, 0x100, 0x110, 0x114, 0x40000c03, 0xffffffff, - SYSC_QUIRK_LEGACY_IDLE), SYSC_QUIRK("uart", 0, 0x50, 0x54, 0x58, 0x00000046, 0xffffffff, SYSC_QUIRK_SWSUP_SIDLE | SYSC_QUIRK_LEGACY_IDLE), SYSC_QUIRK("uart", 0, 0x50, 0x54, 0x58, 0x00000052, 0xffffffff, @@ -1479,7 +1572,10 @@ static const struct sysc_revision_quirk sysc_revision_quirks[] = { SYSC_QUIRK_CLKDM_NOAUTO), SYSC_QUIRK("dwc3", 0x488c0000, 0, 0x10, -ENODEV, 0x500a0200, 0xffffffff, SYSC_QUIRK_CLKDM_NOAUTO), + SYSC_QUIRK("gpio", 0, 0, 0x10, 0x114, 0x50600801, 0xffff00ff, + SYSC_QUIRK_OPT_CLKS_IN_RESET), SYSC_QUIRK("gpmc", 0, 0, 0x10, 0x14, 0x00000060, 0xffffffff, + SYSC_QUIRK_REINIT_ON_CTX_LOST | SYSC_QUIRK_RESET_ON_CTX_LOST | SYSC_QUIRK_GPMC_DEBUG), SYSC_QUIRK("hdmi", 0, 0, 0x10, -ENODEV, 0x50030200, 0xffffffff, SYSC_QUIRK_OPT_CLKS_NEEDED), @@ -1515,10 +1611,11 @@ static const struct sysc_revision_quirk sysc_revision_quirks[] = { SYSC_QUIRK("usb_host_hs", 0, 0, 0x10, -ENODEV, 0x50700101, 0xffffffff, SYSC_QUIRK_SWSUP_SIDLE | SYSC_QUIRK_SWSUP_MSTANDBY), SYSC_QUIRK("usb_otg_hs", 0, 0x400, 0x404, 0x408, 0x00000050, - 0xffffffff, SYSC_QUIRK_SWSUP_SIDLE | SYSC_QUIRK_SWSUP_MSTANDBY), + 0xffffffff, SYSC_QUIRK_SWSUP_SIDLE | SYSC_QUIRK_SWSUP_MSTANDBY | + SYSC_MODULE_QUIRK_OTG), SYSC_QUIRK("usb_otg_hs", 0, 0, 0x10, -ENODEV, 0x4ea2080d, 0xffffffff, SYSC_QUIRK_SWSUP_SIDLE | SYSC_QUIRK_SWSUP_MSTANDBY | - SYSC_QUIRK_REINIT_ON_RESUME), + SYSC_QUIRK_REINIT_ON_CTX_LOST), SYSC_QUIRK("wdt", 0, 0, 0x10, 0x14, 0x502a0500, 0xfffff0f0, SYSC_MODULE_QUIRK_WDT), /* PRUSS on am3, am4 and am5 */ @@ -1583,6 +1680,7 @@ static const struct sysc_revision_quirk sysc_revision_quirks[] = { SYSC_QUIRK("sdio", 0, 0, 0x10, -ENODEV, 0x40202301, 0xffff0ff0, 0), SYSC_QUIRK("sdio", 0, 0x2fc, 0x110, 0x114, 0x31010000, 0xffffffff, 0), SYSC_QUIRK("sdma", 0, 0, 0x2c, 0x28, 0x00010900, 0xffffffff, 0), + SYSC_QUIRK("sham", 0, 0x100, 0x110, 0x114, 0x40000c03, 0xffffffff, 0), SYSC_QUIRK("slimbus", 0, 0, 0x10, -ENODEV, 0x40000902, 0xffffffff, 0), SYSC_QUIRK("slimbus", 0, 0, 0x10, -ENODEV, 0x40002903, 0xffffffff, 0), SYSC_QUIRK("smartreflex", 0, -ENODEV, 0x24, -ENODEV, 0x00000000, 0xffffffff, 0), @@ -1874,6 +1972,22 @@ static void sysc_module_lock_quirk_rtc(struct sysc *ddata) sysc_quirk_rtc(ddata, true); } +/* OTG omap2430 glue layer up to omap4 needs OTG_FORCESTDBY configured */ +static void sysc_module_enable_quirk_otg(struct sysc *ddata) +{ + int offset = 0x414; /* OTG_FORCESTDBY */ + + sysc_write(ddata, offset, 0); +} + +static void sysc_module_disable_quirk_otg(struct sysc *ddata) +{ + int offset = 0x414; /* OTG_FORCESTDBY */ + u32 val = BIT(0); /* ENABLEFORCE */ + + sysc_write(ddata, offset, val); +} + /* 36xx SGX needs a quirk for to bypass OCP IPG interrupt logic */ static void sysc_module_enable_quirk_sgx(struct sysc *ddata) { @@ -1956,6 +2070,11 @@ static void sysc_init_module_quirks(struct sysc *ddata) return; } + if (ddata->cfg.quirks & SYSC_MODULE_QUIRK_OTG) { + ddata->module_enable_quirk = sysc_module_enable_quirk_otg; + ddata->module_disable_quirk = sysc_module_disable_quirk_otg; + } + if (ddata->cfg.quirks & SYSC_MODULE_QUIRK_SGX) ddata->module_enable_quirk = sysc_module_enable_quirk_sgx; @@ -2401,6 +2520,78 @@ static struct dev_pm_domain sysc_child_pm_domain = { } }; +/* Caller needs to take list_lock if ever used outside of cpu_pm */ +static void sysc_reinit_modules(struct sysc_soc_info *soc) +{ + struct sysc_module *module; + struct list_head *pos; + struct sysc *ddata; + + list_for_each(pos, &sysc_soc->restored_modules) { + module = list_entry(pos, struct sysc_module, node); + ddata = module->ddata; + sysc_reinit_module(ddata, ddata->enabled); + } +} + +/** + * sysc_context_notifier - optionally reset and restore module after idle + * @nb: notifier block + * @cmd: unused + * @v: unused + * + * Some interconnect target modules need to be restored, or reset and restored + * on CPU_PM CPU_PM_CLUSTER_EXIT notifier. This is needed at least for am335x + * OTG and GPMC target modules even if the modules are unused. + */ +static int sysc_context_notifier(struct notifier_block *nb, unsigned long cmd, + void *v) +{ + struct sysc_soc_info *soc; + + soc = container_of(nb, struct sysc_soc_info, nb); + + switch (cmd) { + case CPU_CLUSTER_PM_ENTER: + break; + case CPU_CLUSTER_PM_ENTER_FAILED: /* No need to restore context */ + break; + case CPU_CLUSTER_PM_EXIT: + sysc_reinit_modules(soc); + break; + } + + return NOTIFY_OK; +} + +/** + * sysc_add_restored - optionally add reset and restore quirk hanlling + * @ddata: device data + */ +static void sysc_add_restored(struct sysc *ddata) +{ + struct sysc_module *restored_module; + + restored_module = kzalloc(sizeof(*restored_module), GFP_KERNEL); + if (!restored_module) + return; + + restored_module->ddata = ddata; + + mutex_lock(&sysc_soc->list_lock); + + list_add(&restored_module->node, &sysc_soc->restored_modules); + + if (sysc_soc->nb.notifier_call) + goto out_unlock; + + sysc_soc->nb.notifier_call = sysc_context_notifier; + cpu_pm_register_notifier(&sysc_soc->nb); + +out_unlock: + mutex_unlock(&sysc_soc->list_lock); +} + /** * sysc_legacy_idle_quirk - handle children in omap_device compatible way * @ddata: device driver data @@ -2900,12 +3091,14 @@ static int sysc_add_disabled(unsigned long base) } /* - * One time init to detect the booted SoC and disable unavailable features. + * One time init to detect the booted SoC, disable unavailable features + * and initialize list for optional cpu_pm notifier. + * * Note that we initialize static data shared across all ti-sysc instances * so ddata is only used for SoC type. This can be called from module_init * once we no longer need to rely on platform data. */ -static int sysc_init_soc(struct sysc *ddata) +static int sysc_init_static_data(struct sysc *ddata) { const struct soc_device_attribute *match; struct ti_sysc_platform_data *pdata; @@ -2921,6 +3114,7 @@ static int sysc_init_soc(struct sysc *ddata) mutex_init(&sysc_soc->list_lock); INIT_LIST_HEAD(&sysc_soc->disabled_modules); + INIT_LIST_HEAD(&sysc_soc->restored_modules); sysc_soc->general_purpose = true; pdata = dev_get_platdata(ddata->dev); @@ -2985,15 +3179,24 @@ static int sysc_init_soc(struct sysc *ddata) return 0; } -static void sysc_cleanup_soc(void) +static void sysc_cleanup_static_data(void) { + struct sysc_module *restored_module; struct sysc_address *disabled_module; struct list_head *pos, *tmp; if (!sysc_soc) return; + if (sysc_soc->nb.notifier_call) + cpu_pm_unregister_notifier(&sysc_soc->nb); + mutex_lock(&sysc_soc->list_lock); + list_for_each_safe(pos, tmp, &sysc_soc->restored_modules) { + restored_module = list_entry(pos, struct sysc_module, node); + list_del(pos); + kfree(restored_module); + } list_for_each_safe(pos, tmp, &sysc_soc->disabled_modules) { disabled_module = list_entry(pos, struct sysc_address, node); list_del(pos); @@ -3061,7 +3264,7 @@ static int sysc_probe(struct platform_device *pdev) ddata->dev = &pdev->dev; platform_set_drvdata(pdev, ddata); - error = sysc_init_soc(ddata); + error = sysc_init_static_data(ddata); if (error) return error; @@ -3159,6 +3362,9 @@ static int sysc_probe(struct platform_device *pdev) pm_runtime_put(&pdev->dev); } + if (ddata->cfg.quirks & SYSC_QUIRK_REINIT_ON_CTX_LOST) + sysc_add_restored(ddata); + return 0; err: @@ -3240,7 +3446,7 @@ static void __exit sysc_exit(void) { bus_unregister_notifier(&platform_bus_type, &sysc_nb); platform_driver_unregister(&sysc_driver); - sysc_cleanup_soc(); + sysc_cleanup_static_data(); } module_exit(sysc_exit); diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm index 334f83e56120..15d6c46c0a47 100644 --- a/drivers/cpuidle/Kconfig.arm +++ b/drivers/cpuidle/Kconfig.arm @@ -99,7 +99,7 @@ config ARM_MVEBU_V7_CPUIDLE config ARM_TEGRA_CPUIDLE bool "CPU Idle Driver for NVIDIA Tegra SoCs" - depends on ARCH_TEGRA && !ARM64 + depends on (ARCH_TEGRA || COMPILE_TEST) && !ARM64 && MMU select ARCH_NEEDS_CPU_IDLE_COUPLED if SMP select ARM_CPU_SUSPEND help @@ -112,6 +112,7 @@ config ARM_QCOM_SPM_CPUIDLE select CPU_IDLE_MULTIPLE_DRIVERS select DT_IDLE_STATES select QCOM_SCM + select QCOM_SPM help Select this to enable cpuidle for Qualcomm processors. The Subsystem Power Manager (SPM) controls low power modes for the diff --git a/drivers/cpuidle/cpuidle-qcom-spm.c b/drivers/cpuidle/cpuidle-qcom-spm.c index c0e7971da2da..01e77913a414 100644 --- a/drivers/cpuidle/cpuidle-qcom-spm.c +++ b/drivers/cpuidle/cpuidle-qcom-spm.c @@ -18,158 +18,18 @@ #include <linux/cpuidle.h> #include <linux/cpu_pm.h> #include <linux/qcom_scm.h> +#include <soc/qcom/spm.h> #include <asm/proc-fns.h> #include <asm/suspend.h> #include "dt_idle_states.h" -#define MAX_PMIC_DATA 2 -#define MAX_SEQ_DATA 64 -#define SPM_CTL_INDEX 0x7f -#define SPM_CTL_INDEX_SHIFT 4 -#define SPM_CTL_EN BIT(0) - -enum pm_sleep_mode { - PM_SLEEP_MODE_STBY, - PM_SLEEP_MODE_RET, - PM_SLEEP_MODE_SPC, - PM_SLEEP_MODE_PC, - PM_SLEEP_MODE_NR, -}; - -enum spm_reg { - SPM_REG_CFG, - SPM_REG_SPM_CTL, - SPM_REG_DLY, - SPM_REG_PMIC_DLY, - SPM_REG_PMIC_DATA_0, - SPM_REG_PMIC_DATA_1, - SPM_REG_VCTL, - SPM_REG_SEQ_ENTRY, - SPM_REG_SPM_STS, - SPM_REG_PMIC_STS, - SPM_REG_NR, -}; - -struct spm_reg_data { - const u8 *reg_offset; - u32 spm_cfg; - u32 spm_dly; - u32 pmic_dly; - u32 pmic_data[MAX_PMIC_DATA]; - u8 seq[MAX_SEQ_DATA]; - u8 start_index[PM_SLEEP_MODE_NR]; -}; - -struct spm_driver_data { +struct cpuidle_qcom_spm_data { struct cpuidle_driver cpuidle_driver; - void __iomem *reg_base; - const struct spm_reg_data *reg_data; -}; - -static const u8 spm_reg_offset_v2_1[SPM_REG_NR] = { - [SPM_REG_CFG] = 0x08, - [SPM_REG_SPM_CTL] = 0x30, - [SPM_REG_DLY] = 0x34, - [SPM_REG_SEQ_ENTRY] = 0x80, -}; - -/* SPM register data for 8974, 8084 */ -static const struct spm_reg_data spm_reg_8974_8084_cpu = { - .reg_offset = spm_reg_offset_v2_1, - .spm_cfg = 0x1, - .spm_dly = 0x3C102800, - .seq = { 0x03, 0x0B, 0x0F, 0x00, 0x20, 0x80, 0x10, 0xE8, 0x5B, 0x03, - 0x3B, 0xE8, 0x5B, 0x82, 0x10, 0x0B, 0x30, 0x06, 0x26, 0x30, - 0x0F }, - .start_index[PM_SLEEP_MODE_STBY] = 0, - .start_index[PM_SLEEP_MODE_SPC] = 3, + struct spm_driver_data *spm; }; -/* SPM register data for 8226 */ -static const struct spm_reg_data spm_reg_8226_cpu = { - .reg_offset = spm_reg_offset_v2_1, - .spm_cfg = 0x0, - .spm_dly = 0x3C102800, - .seq = { 0x60, 0x03, 0x60, 0x0B, 0x0F, 0x20, 0x10, 0x80, 0x30, 0x90, - 0x5B, 0x60, 0x03, 0x60, 0x3B, 0x76, 0x76, 0x0B, 0x94, 0x5B, - 0x80, 0x10, 0x26, 0x30, 0x0F }, - .start_index[PM_SLEEP_MODE_STBY] = 0, - .start_index[PM_SLEEP_MODE_SPC] = 5, -}; - -static const u8 spm_reg_offset_v1_1[SPM_REG_NR] = { - [SPM_REG_CFG] = 0x08, - [SPM_REG_SPM_CTL] = 0x20, - [SPM_REG_PMIC_DLY] = 0x24, - [SPM_REG_PMIC_DATA_0] = 0x28, - [SPM_REG_PMIC_DATA_1] = 0x2C, - [SPM_REG_SEQ_ENTRY] = 0x80, -}; - -/* SPM register data for 8064 */ -static const struct spm_reg_data spm_reg_8064_cpu = { - .reg_offset = spm_reg_offset_v1_1, - .spm_cfg = 0x1F, - .pmic_dly = 0x02020004, - .pmic_data[0] = 0x0084009C, - .pmic_data[1] = 0x00A4001C, - .seq = { 0x03, 0x0F, 0x00, 0x24, 0x54, 0x10, 0x09, 0x03, 0x01, - 0x10, 0x54, 0x30, 0x0C, 0x24, 0x30, 0x0F }, - .start_index[PM_SLEEP_MODE_STBY] = 0, - .start_index[PM_SLEEP_MODE_SPC] = 2, -}; - -static inline void spm_register_write(struct spm_driver_data *drv, - enum spm_reg reg, u32 val) -{ - if (drv->reg_data->reg_offset[reg]) - writel_relaxed(val, drv->reg_base + - drv->reg_data->reg_offset[reg]); -} - -/* Ensure a guaranteed write, before return */ -static inline void spm_register_write_sync(struct spm_driver_data *drv, - enum spm_reg reg, u32 val) -{ - u32 ret; - - if (!drv->reg_data->reg_offset[reg]) - return; - - do { - writel_relaxed(val, drv->reg_base + - drv->reg_data->reg_offset[reg]); - ret = readl_relaxed(drv->reg_base + - drv->reg_data->reg_offset[reg]); - if (ret == val) - break; - cpu_relax(); - } while (1); -} - -static inline u32 spm_register_read(struct spm_driver_data *drv, - enum spm_reg reg) -{ - return readl_relaxed(drv->reg_base + drv->reg_data->reg_offset[reg]); -} - -static void spm_set_low_power_mode(struct spm_driver_data *drv, - enum pm_sleep_mode mode) -{ - u32 start_index; - u32 ctl_val; - - start_index = drv->reg_data->start_index[mode]; - - ctl_val = spm_register_read(drv, SPM_REG_SPM_CTL); - ctl_val &= ~(SPM_CTL_INDEX << SPM_CTL_INDEX_SHIFT); - ctl_val |= start_index << SPM_CTL_INDEX_SHIFT; - ctl_val |= SPM_CTL_EN; - spm_register_write_sync(drv, SPM_REG_SPM_CTL, ctl_val); -} - static int qcom_pm_collapse(unsigned long int unused) { qcom_scm_cpu_power_down(QCOM_SCM_CPU_PWR_DOWN_L2_ON); @@ -201,10 +61,10 @@ static int qcom_cpu_spc(struct spm_driver_data *drv) static int spm_enter_idle_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, int idx) { - struct spm_driver_data *data = container_of(drv, struct spm_driver_data, - cpuidle_driver); + struct cpuidle_qcom_spm_data *data = container_of(drv, struct cpuidle_qcom_spm_data, + cpuidle_driver); - return CPU_PM_CPU_IDLE_ENTER_PARAM(qcom_cpu_spc, idx, data); + return CPU_PM_CPU_IDLE_ENTER_PARAM(qcom_cpu_spc, idx, data->spm); } static struct cpuidle_driver qcom_spm_idle_driver = { @@ -225,134 +85,92 @@ static const struct of_device_id qcom_idle_state_match[] = { { }, }; -static int spm_cpuidle_init(struct cpuidle_driver *drv, int cpu) +static int spm_cpuidle_register(struct device *cpuidle_dev, int cpu) { + struct platform_device *pdev = NULL; + struct device_node *cpu_node, *saw_node; + struct cpuidle_qcom_spm_data *data = NULL; int ret; - memcpy(drv, &qcom_spm_idle_driver, sizeof(*drv)); - drv->cpumask = (struct cpumask *)cpumask_of(cpu); + cpu_node = of_cpu_device_node_get(cpu); + if (!cpu_node) + return -ENODEV; - /* Parse idle states from device tree */ - ret = dt_init_idle_driver(drv, qcom_idle_state_match, 1); - if (ret <= 0) - return ret ? : -ENODEV; + saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0); + if (!saw_node) + return -ENODEV; - /* We have atleast one power down mode */ - return qcom_scm_set_warm_boot_addr(cpu_resume_arm, drv->cpumask); -} + pdev = of_find_device_by_node(saw_node); + of_node_put(saw_node); + of_node_put(cpu_node); + if (!pdev) + return -ENODEV; -static struct spm_driver_data *spm_get_drv(struct platform_device *pdev, - int *spm_cpu) -{ - struct spm_driver_data *drv = NULL; - struct device_node *cpu_node, *saw_node; - int cpu; - bool found = 0; + data = devm_kzalloc(cpuidle_dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; - for_each_possible_cpu(cpu) { - cpu_node = of_cpu_device_node_get(cpu); - if (!cpu_node) - continue; - saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0); - found = (saw_node == pdev->dev.of_node); - of_node_put(saw_node); - of_node_put(cpu_node); - if (found) - break; - } + data->spm = dev_get_drvdata(&pdev->dev); + if (!data->spm) + return -EINVAL; - if (found) { - drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); - if (drv) - *spm_cpu = cpu; - } + data->cpuidle_driver = qcom_spm_idle_driver; + data->cpuidle_driver.cpumask = (struct cpumask *)cpumask_of(cpu); - return drv; -} + ret = dt_init_idle_driver(&data->cpuidle_driver, + qcom_idle_state_match, 1); + if (ret <= 0) + return ret ? : -ENODEV; -static const struct of_device_id spm_match_table[] = { - { .compatible = "qcom,msm8226-saw2-v2.1-cpu", - .data = &spm_reg_8226_cpu }, - { .compatible = "qcom,msm8974-saw2-v2.1-cpu", - .data = &spm_reg_8974_8084_cpu }, - { .compatible = "qcom,apq8084-saw2-v2.1-cpu", - .data = &spm_reg_8974_8084_cpu }, - { .compatible = "qcom,apq8064-saw2-v1.1-cpu", - .data = &spm_reg_8064_cpu }, - { }, -}; + ret = qcom_scm_set_warm_boot_addr(cpu_resume_arm, cpumask_of(cpu)); + if (ret) + return ret; + + return cpuidle_register(&data->cpuidle_driver, NULL); +} -static int spm_dev_probe(struct platform_device *pdev) +static int spm_cpuidle_drv_probe(struct platform_device *pdev) { - struct spm_driver_data *drv; - struct resource *res; - const struct of_device_id *match_id; - void __iomem *addr; int cpu, ret; if (!qcom_scm_is_available()) return -EPROBE_DEFER; - drv = spm_get_drv(pdev, &cpu); - if (!drv) - return -EINVAL; - platform_set_drvdata(pdev, drv); + for_each_possible_cpu(cpu) { + ret = spm_cpuidle_register(&pdev->dev, cpu); + if (ret && ret != -ENODEV) { + dev_err(&pdev->dev, + "Cannot register for CPU%d: %d\n", cpu, ret); + } + } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - drv->reg_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(drv->reg_base)) - return PTR_ERR(drv->reg_base); + return 0; +} - match_id = of_match_node(spm_match_table, pdev->dev.of_node); - if (!match_id) - return -ENODEV; +static struct platform_driver spm_cpuidle_driver = { + .probe = spm_cpuidle_drv_probe, + .driver = { + .name = "qcom-spm-cpuidle", + .suppress_bind_attrs = true, + }, +}; - drv->reg_data = match_id->data; +static int __init qcom_spm_cpuidle_init(void) +{ + struct platform_device *pdev; + int ret; - ret = spm_cpuidle_init(&drv->cpuidle_driver, cpu); + ret = platform_driver_register(&spm_cpuidle_driver); if (ret) return ret; - /* Write the SPM sequences first.. */ - addr = drv->reg_base + drv->reg_data->reg_offset[SPM_REG_SEQ_ENTRY]; - __iowrite32_copy(addr, drv->reg_data->seq, - ARRAY_SIZE(drv->reg_data->seq) / 4); - - /* - * ..and then the control registers. - * On some SoC if the control registers are written first and if the - * CPU was held in reset, the reset signal could trigger the SPM state - * machine, before the sequences are completely written. - */ - spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg); - spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly); - spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly); - spm_register_write(drv, SPM_REG_PMIC_DATA_0, - drv->reg_data->pmic_data[0]); - spm_register_write(drv, SPM_REG_PMIC_DATA_1, - drv->reg_data->pmic_data[1]); - - /* Set up Standby as the default low power mode */ - spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY); - - return cpuidle_register(&drv->cpuidle_driver, NULL); -} - -static int spm_dev_remove(struct platform_device *pdev) -{ - struct spm_driver_data *drv = platform_get_drvdata(pdev); + pdev = platform_device_register_simple("qcom-spm-cpuidle", + -1, NULL, 0); + if (IS_ERR(pdev)) { + platform_driver_unregister(&spm_cpuidle_driver); + return PTR_ERR(pdev); + } - cpuidle_unregister(&drv->cpuidle_driver); return 0; } - -static struct platform_driver spm_driver = { - .probe = spm_dev_probe, - .remove = spm_dev_remove, - .driver = { - .name = "saw", - .of_match_table = spm_match_table, - }, -}; - -builtin_platform_driver(spm_driver); +device_initcall(qcom_spm_cpuidle_init); diff --git a/drivers/cpuidle/cpuidle-tegra.c b/drivers/cpuidle/cpuidle-tegra.c index 508bd9f23792..9845629aeb6d 100644 --- a/drivers/cpuidle/cpuidle-tegra.c +++ b/drivers/cpuidle/cpuidle-tegra.c @@ -337,6 +337,9 @@ static void tegra_cpuidle_setup_tegra114_c7_state(void) static int tegra_cpuidle_probe(struct platform_device *pdev) { + if (tegra_pmc_get_suspend_mode() == TEGRA_SUSPEND_NOT_READY) + return -EPROBE_DEFER; + /* LP2 could be disabled in device-tree */ if (tegra_pmc_get_suspend_mode() < TEGRA_SUSPEND_LP2) tegra_cpuidle_disable_state(TEGRA_CC6); diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c index c9fb56afbcb4..14f900047ac0 100644 --- a/drivers/firmware/arm_ffa/driver.c +++ b/drivers/firmware/arm_ffa/driver.c @@ -167,6 +167,27 @@ struct ffa_drv_info { static struct ffa_drv_info *drv_info; +/* + * The driver must be able to support all the versions from the earliest + * supported FFA_MIN_VERSION to the latest supported FFA_DRIVER_VERSION. + * The specification states that if firmware supports a FFA implementation + * that is incompatible with and at a greater version number than specified + * by the caller(FFA_DRIVER_VERSION passed as parameter to FFA_VERSION), + * it must return the NOT_SUPPORTED error code. + */ +static u32 ffa_compatible_version_find(u32 version) +{ + u16 major = MAJOR_VERSION(version), minor = MINOR_VERSION(version); + u16 drv_major = MAJOR_VERSION(FFA_DRIVER_VERSION); + u16 drv_minor = MINOR_VERSION(FFA_DRIVER_VERSION); + + if ((major < drv_major) || (major == drv_major && minor <= drv_minor)) + return version; + + pr_info("Firmware version higher than driver version, downgrading\n"); + return FFA_DRIVER_VERSION; +} + static int ffa_version_check(u32 *version) { ffa_value_t ver; @@ -180,15 +201,20 @@ static int ffa_version_check(u32 *version) return -EOPNOTSUPP; } - if (ver.a0 < FFA_MIN_VERSION || ver.a0 > FFA_DRIVER_VERSION) { - pr_err("Incompatible version %d.%d found\n", - MAJOR_VERSION(ver.a0), MINOR_VERSION(ver.a0)); + if (ver.a0 < FFA_MIN_VERSION) { + pr_err("Incompatible v%d.%d! Earliest supported v%d.%d\n", + MAJOR_VERSION(ver.a0), MINOR_VERSION(ver.a0), + MAJOR_VERSION(FFA_MIN_VERSION), + MINOR_VERSION(FFA_MIN_VERSION)); return -EINVAL; } - *version = ver.a0; - pr_info("Version %d.%d found\n", MAJOR_VERSION(ver.a0), + pr_info("Driver version %d.%d\n", MAJOR_VERSION(FFA_DRIVER_VERSION), + MINOR_VERSION(FFA_DRIVER_VERSION)); + pr_info("Firmware version %d.%d found\n", MAJOR_VERSION(ver.a0), MINOR_VERSION(ver.a0)); + *version = ffa_compatible_version_find(ver.a0); + return 0; } @@ -586,6 +612,22 @@ ffa_memory_share(struct ffa_device *dev, struct ffa_mem_ops_args *args) return ffa_memory_ops(FFA_FN_NATIVE(MEM_SHARE), args); } +static int +ffa_memory_lend(struct ffa_device *dev, struct ffa_mem_ops_args *args) +{ + /* Note that upon a successful MEM_LEND request the caller + * must ensure that the memory region specified is not accessed + * until a successful MEM_RECALIM call has been made. + * On systems with a hypervisor present this will been enforced, + * however on systems without a hypervisor the responsibility + * falls to the calling kernel driver to prevent access. + */ + if (dev->mode_32bit) + return ffa_memory_ops(FFA_MEM_LEND, args); + + return ffa_memory_ops(FFA_FN_NATIVE(MEM_LEND), args); +} + static const struct ffa_dev_ops ffa_ops = { .api_version_get = ffa_api_version_get, .partition_info_get = ffa_partition_info_get, @@ -593,6 +635,7 @@ static const struct ffa_dev_ops ffa_ops = { .sync_send_receive = ffa_sync_send_receive, .memory_reclaim = ffa_memory_reclaim, .memory_share = ffa_memory_share, + .memory_lend = ffa_memory_lend, }; const struct ffa_dev_ops *ffa_dev_ops_get(struct ffa_device *dev) diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index 2ee97bab7440..7db8066b19fd 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -252,7 +252,7 @@ static bool __qcom_scm_is_call_available(struct device *dev, u32 svc_id, break; default: pr_err("Unknown SMC convention being used\n"); - return -EINVAL; + return false; } ret = qcom_scm_call(dev, &desc, &res); @@ -1348,6 +1348,10 @@ static const struct of_device_id qcom_scm_dt_match[] = { SCM_HAS_IFACE_CLK | SCM_HAS_BUS_CLK) }, + { .compatible = "qcom,scm-msm8953", .data = (void *)(SCM_HAS_CORE_CLK | + SCM_HAS_IFACE_CLK | + SCM_HAS_BUS_CLK) + }, { .compatible = "qcom,scm-msm8974", .data = (void *)(SCM_HAS_CORE_CLK | SCM_HAS_IFACE_CLK | SCM_HAS_BUS_CLK) diff --git a/drivers/firmware/tegra/bpmp-debugfs.c b/drivers/firmware/tegra/bpmp-debugfs.c index 3e9fa4b54358..6d66fe03fb6a 100644 --- a/drivers/firmware/tegra/bpmp-debugfs.c +++ b/drivers/firmware/tegra/bpmp-debugfs.c @@ -74,28 +74,36 @@ static void seqbuf_seek(struct seqbuf *seqbuf, ssize_t offset) static const char *get_filename(struct tegra_bpmp *bpmp, const struct file *file, char *buf, int size) { - char root_path_buf[512]; - const char *root_path; - const char *filename; + const char *root_path, *filename = NULL; + char *root_path_buf; size_t root_len; + root_path_buf = kzalloc(512, GFP_KERNEL); + if (!root_path_buf) + goto out; + root_path = dentry_path(bpmp->debugfs_mirror, root_path_buf, sizeof(root_path_buf)); if (IS_ERR(root_path)) - return NULL; + goto out; root_len = strlen(root_path); filename = dentry_path(file->f_path.dentry, buf, size); - if (IS_ERR(filename)) - return NULL; + if (IS_ERR(filename)) { + filename = NULL; + goto out; + } - if (strlen(filename) < root_len || - strncmp(filename, root_path, root_len)) - return NULL; + if (strlen(filename) < root_len || strncmp(filename, root_path, root_len)) { + filename = NULL; + goto out; + } filename += root_len; +out: + kfree(root_path_buf); return filename; } diff --git a/drivers/firmware/tegra/bpmp-tegra210.c b/drivers/firmware/tegra/bpmp-tegra210.c index c32754055c60..c9c830f658c3 100644 --- a/drivers/firmware/tegra/bpmp-tegra210.c +++ b/drivers/firmware/tegra/bpmp-tegra210.c @@ -162,7 +162,6 @@ static int tegra210_bpmp_init(struct tegra_bpmp *bpmp) { struct platform_device *pdev = to_platform_device(bpmp->dev); struct tegra210_bpmp *priv; - struct resource *res; unsigned int i; int err; @@ -172,13 +171,11 @@ static int tegra210_bpmp_init(struct tegra_bpmp *bpmp) bpmp->priv = priv; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->atomics = devm_ioremap_resource(&pdev->dev, res); + priv->atomics = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->atomics)) return PTR_ERR(priv->atomics); - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - priv->arb_sema = devm_ioremap_resource(&pdev->dev, res); + priv->arb_sema = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(priv->arb_sema)) return PTR_ERR(priv->arb_sema); diff --git a/drivers/gpu/drm/mediatek/mtk_dsi.c b/drivers/gpu/drm/mediatek/mtk_dsi.c index 93b40c245f00..5d90d2eb0019 100644 --- a/drivers/gpu/drm/mediatek/mtk_dsi.c +++ b/drivers/gpu/drm/mediatek/mtk_dsi.c @@ -11,6 +11,7 @@ #include <linux/of_platform.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> +#include <linux/reset.h> #include <video/mipi_display.h> #include <video/videomode.h> @@ -980,8 +981,10 @@ static int mtk_dsi_bind(struct device *dev, struct device *master, void *data) struct mtk_dsi *dsi = dev_get_drvdata(dev); ret = mtk_dsi_encoder_init(drm, dsi); + if (ret) + return ret; - return ret; + return device_reset_optional(dev); } static void mtk_dsi_unbind(struct device *dev, struct device *master, diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 72c0df129d5c..30bff6cb1b8d 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -55,8 +55,8 @@ config ATMEL_EBI SRAMs, ATA devices, etc. config BRCMSTB_DPFE - bool "Broadcom STB DPFE driver" if COMPILE_TEST - default y if ARCH_BRCMSTB + tristate "Broadcom STB DPFE driver" + default ARCH_BRCMSTB depends on ARCH_BRCMSTB || COMPILE_TEST help This driver provides access to the DPFE interface of Broadcom @@ -210,6 +210,7 @@ config RENESAS_RPCIF tristate "Renesas RPC-IF driver" depends on ARCH_RENESAS || COMPILE_TEST select REGMAP_MMIO + select RESET_CONTROLLER help This supports Renesas R-Car Gen3 or RZ/G2 RPC-IF which provides either SPI host or HyperFlash. You'll have to select individual diff --git a/drivers/memory/fsl_ifc.c b/drivers/memory/fsl_ifc.c index d062c2f8250f..75a8c38df939 100644 --- a/drivers/memory/fsl_ifc.c +++ b/drivers/memory/fsl_ifc.c @@ -263,7 +263,7 @@ static int fsl_ifc_ctrl_probe(struct platform_device *dev) ret = fsl_ifc_ctrl_init(fsl_ifc_ctrl_dev); if (ret < 0) - goto err; + goto err_unmap_nandirq; init_waitqueue_head(&fsl_ifc_ctrl_dev->nand_wait); @@ -272,7 +272,7 @@ static int fsl_ifc_ctrl_probe(struct platform_device *dev) if (ret != 0) { dev_err(&dev->dev, "failed to install irq (%d)\n", fsl_ifc_ctrl_dev->irq); - goto err_irq; + goto err_unmap_nandirq; } if (fsl_ifc_ctrl_dev->nand_irq) { @@ -281,17 +281,16 @@ static int fsl_ifc_ctrl_probe(struct platform_device *dev) if (ret != 0) { dev_err(&dev->dev, "failed to install irq (%d)\n", fsl_ifc_ctrl_dev->nand_irq); - goto err_nandirq; + goto err_free_irq; } } return 0; -err_nandirq: - free_irq(fsl_ifc_ctrl_dev->nand_irq, fsl_ifc_ctrl_dev); - irq_dispose_mapping(fsl_ifc_ctrl_dev->nand_irq); -err_irq: +err_free_irq: free_irq(fsl_ifc_ctrl_dev->irq, fsl_ifc_ctrl_dev); +err_unmap_nandirq: + irq_dispose_mapping(fsl_ifc_ctrl_dev->nand_irq); irq_dispose_mapping(fsl_ifc_ctrl_dev->irq); err: iounmap(fsl_ifc_ctrl_dev->gregs); diff --git a/drivers/memory/jedec_ddr.h b/drivers/memory/jedec_ddr.h index e59ccbd982d0..6cd508478b14 100644 --- a/drivers/memory/jedec_ddr.h +++ b/drivers/memory/jedec_ddr.h @@ -112,6 +112,26 @@ #define NUM_DDR_ADDR_TABLE_ENTRIES 11 #define NUM_DDR_TIMING_TABLE_ENTRIES 4 +#define LPDDR2_MANID_SAMSUNG 1 +#define LPDDR2_MANID_QIMONDA 2 +#define LPDDR2_MANID_ELPIDA 3 +#define LPDDR2_MANID_ETRON 4 +#define LPDDR2_MANID_NANYA 5 +#define LPDDR2_MANID_HYNIX 6 +#define LPDDR2_MANID_MOSEL 7 +#define LPDDR2_MANID_WINBOND 8 +#define LPDDR2_MANID_ESMT 9 +#define LPDDR2_MANID_SPANSION 11 +#define LPDDR2_MANID_SST 12 +#define LPDDR2_MANID_ZMOS 13 +#define LPDDR2_MANID_INTEL 14 +#define LPDDR2_MANID_NUMONYX 254 +#define LPDDR2_MANID_MICRON 255 + +#define LPDDR2_TYPE_S4 0 +#define LPDDR2_TYPE_S2 1 +#define LPDDR2_TYPE_NVM 2 + /* Structure for DDR addressing info from the JEDEC spec */ struct lpddr2_addressing { u32 num_banks; @@ -170,6 +190,33 @@ extern const struct lpddr2_timings lpddr2_jedec_timings[NUM_DDR_TIMING_TABLE_ENTRIES]; extern const struct lpddr2_min_tck lpddr2_jedec_min_tck; +/* Structure of MR8 */ +union lpddr2_basic_config4 { + u32 value; + + struct { + unsigned int arch_type : 2; + unsigned int density : 4; + unsigned int io_width : 2; + } __packed; +}; + +/* + * Structure for information about LPDDR2 chip. All parameters are + * matching raw values of standard mode register bitfields or set to + * -ENOENT if info unavailable. + */ +struct lpddr2_info { + int arch_type; + int density; + int io_width; + int manufacturer_id; + int revision_id1; + int revision_id2; +}; + +const char *lpddr2_jedec_manufacturer(unsigned int manufacturer_id); + /* * Structure for timings for LPDDR3 based on LPDDR2 plus additional fields. * All parameters are in pico seconds(ps) excluding max_freq, min_freq which diff --git a/drivers/memory/jedec_ddr_data.c b/drivers/memory/jedec_ddr_data.c index ed601d813175..2cca4fa188f9 100644 --- a/drivers/memory/jedec_ddr_data.c +++ b/drivers/memory/jedec_ddr_data.c @@ -131,3 +131,44 @@ const struct lpddr2_min_tck lpddr2_jedec_min_tck = { .tFAW = 8 }; EXPORT_SYMBOL_GPL(lpddr2_jedec_min_tck); + +const char *lpddr2_jedec_manufacturer(unsigned int manufacturer_id) +{ + switch (manufacturer_id) { + case LPDDR2_MANID_SAMSUNG: + return "Samsung"; + case LPDDR2_MANID_QIMONDA: + return "Qimonda"; + case LPDDR2_MANID_ELPIDA: + return "Elpida"; + case LPDDR2_MANID_ETRON: + return "Etron"; + case LPDDR2_MANID_NANYA: + return "Nanya"; + case LPDDR2_MANID_HYNIX: + return "Hynix"; + case LPDDR2_MANID_MOSEL: + return "Mosel"; + case LPDDR2_MANID_WINBOND: + return "Winbond"; + case LPDDR2_MANID_ESMT: + return "ESMT"; + case LPDDR2_MANID_SPANSION: + return "Spansion"; + case LPDDR2_MANID_SST: + return "SST"; + case LPDDR2_MANID_ZMOS: + return "ZMOS"; + case LPDDR2_MANID_INTEL: + return "Intel"; + case LPDDR2_MANID_NUMONYX: + return "Numonyx"; + case LPDDR2_MANID_MICRON: + return "Micron"; + default: + break; + } + + return "invalid"; +} +EXPORT_SYMBOL_GPL(lpddr2_jedec_manufacturer); diff --git a/drivers/memory/mtk-smi.c b/drivers/memory/mtk-smi.c index c5fb51f73b34..b883dcc0bbfa 100644 --- a/drivers/memory/mtk-smi.c +++ b/drivers/memory/mtk-smi.c @@ -17,13 +17,33 @@ #include <dt-bindings/memory/mt2701-larb-port.h> #include <dt-bindings/memory/mtk-memory-port.h> -/* mt8173 */ -#define SMI_LARB_MMU_EN 0xf00 +/* SMI COMMON */ +#define SMI_L1LEN 0x100 -/* mt8167 */ -#define MT8167_SMI_LARB_MMU_EN 0xfc0 +#define SMI_BUS_SEL 0x220 +#define SMI_BUS_LARB_SHIFT(larbid) ((larbid) << 1) +/* All are MMU0 defaultly. Only specialize mmu1 here. */ +#define F_MMU1_LARB(larbid) (0x1 << SMI_BUS_LARB_SHIFT(larbid)) + +#define SMI_M4U_TH 0x234 +#define SMI_FIFO_TH1 0x238 +#define SMI_FIFO_TH2 0x23c +#define SMI_DCM 0x300 +#define SMI_DUMMY 0x444 -/* mt2701 */ +/* SMI LARB */ +#define SMI_LARB_CMD_THRT_CON 0x24 +#define SMI_LARB_THRT_RD_NU_LMT_MSK GENMASK(7, 4) +#define SMI_LARB_THRT_RD_NU_LMT (5 << 4) + +#define SMI_LARB_SW_FLAG 0x40 +#define SMI_LARB_SW_FLAG_1 0x1 + +#define SMI_LARB_OSTDL_PORT 0x200 +#define SMI_LARB_OSTDL_PORTx(id) (SMI_LARB_OSTDL_PORT + (((id) & 0x1f) << 2)) + +/* Below are about mmu enable registers, they are different in SoCs */ +/* gen1: mt2701 */ #define REG_SMI_SECUR_CON_BASE 0x5c0 /* every register control 8 port, register offset 0x4 */ @@ -41,99 +61,94 @@ /* mt2701 domain should be set to 3 */ #define SMI_SECUR_CON_VAL_DOMAIN(id) (0x3 << ((((id) & 0x7) << 2) + 1)) -/* mt2712 */ -#define SMI_LARB_NONSEC_CON(id) (0x380 + ((id) * 4)) -#define F_MMU_EN BIT(0) -#define BANK_SEL(id) ({ \ +/* gen2: */ +/* mt8167 */ +#define MT8167_SMI_LARB_MMU_EN 0xfc0 + +/* mt8173 */ +#define MT8173_SMI_LARB_MMU_EN 0xf00 + +/* general */ +#define SMI_LARB_NONSEC_CON(id) (0x380 + ((id) * 4)) +#define F_MMU_EN BIT(0) +#define BANK_SEL(id) ({ \ u32 _id = (id) & 0x3; \ (_id << 8 | _id << 10 | _id << 12 | _id << 14); \ }) -/* SMI COMMON */ -#define SMI_BUS_SEL 0x220 -#define SMI_BUS_LARB_SHIFT(larbid) ((larbid) << 1) -/* All are MMU0 defaultly. Only specialize mmu1 here. */ -#define F_MMU1_LARB(larbid) (0x1 << SMI_BUS_LARB_SHIFT(larbid)) +#define SMI_COMMON_INIT_REGS_NR 6 +#define SMI_LARB_PORT_NR_MAX 32 + +#define MTK_SMI_FLAG_THRT_UPDATE BIT(0) +#define MTK_SMI_FLAG_SW_FLAG BIT(1) +#define MTK_SMI_CAPS(flags, _x) (!!((flags) & (_x))) + +struct mtk_smi_reg_pair { + unsigned int offset; + u32 value; +}; -enum mtk_smi_gen { +enum mtk_smi_type { MTK_SMI_GEN1, - MTK_SMI_GEN2 + MTK_SMI_GEN2, /* gen2 smi common */ + MTK_SMI_GEN2_SUB_COMM, /* gen2 smi sub common */ }; +#define MTK_SMI_CLK_NR_MAX 4 + +/* larbs: Require apb/smi clocks while gals is optional. */ +static const char * const mtk_smi_larb_clks[] = {"apb", "smi", "gals"}; +#define MTK_SMI_LARB_REQ_CLK_NR 2 +#define MTK_SMI_LARB_OPT_CLK_NR 1 + +/* + * common: Require these four clocks in has_gals case. Otherwise, only apb/smi are required. + * sub common: Require apb/smi/gals0 clocks in has_gals case. Otherwise, only apb/smi are required. + */ +static const char * const mtk_smi_common_clks[] = {"apb", "smi", "gals0", "gals1"}; +#define MTK_SMI_COM_REQ_CLK_NR 2 +#define MTK_SMI_COM_GALS_REQ_CLK_NR MTK_SMI_CLK_NR_MAX +#define MTK_SMI_SUB_COM_GALS_REQ_CLK_NR 3 + struct mtk_smi_common_plat { - enum mtk_smi_gen gen; - bool has_gals; - u32 bus_sel; /* Balance some larbs to enter mmu0 or mmu1 */ + enum mtk_smi_type type; + bool has_gals; + u32 bus_sel; /* Balance some larbs to enter mmu0 or mmu1 */ + + const struct mtk_smi_reg_pair *init; }; struct mtk_smi_larb_gen { int port_in_larb[MTK_LARB_NR_MAX + 1]; void (*config_port)(struct device *dev); unsigned int larb_direct_to_common_mask; - bool has_gals; + unsigned int flags_general; + const u8 (*ostd)[SMI_LARB_PORT_NR_MAX]; }; struct mtk_smi { struct device *dev; - struct clk *clk_apb, *clk_smi; - struct clk *clk_gals0, *clk_gals1; + unsigned int clk_num; + struct clk_bulk_data clks[MTK_SMI_CLK_NR_MAX]; struct clk *clk_async; /*only needed by mt2701*/ union { void __iomem *smi_ao_base; /* only for gen1 */ void __iomem *base; /* only for gen2 */ }; + struct device *smi_common_dev; /* for sub common */ const struct mtk_smi_common_plat *plat; }; struct mtk_smi_larb { /* larb: local arbiter */ struct mtk_smi smi; void __iomem *base; - struct device *smi_common_dev; + struct device *smi_common_dev; /* common or sub-common dev */ const struct mtk_smi_larb_gen *larb_gen; int larbid; u32 *mmu; unsigned char *bank; }; -static int mtk_smi_clk_enable(const struct mtk_smi *smi) -{ - int ret; - - ret = clk_prepare_enable(smi->clk_apb); - if (ret) - return ret; - - ret = clk_prepare_enable(smi->clk_smi); - if (ret) - goto err_disable_apb; - - ret = clk_prepare_enable(smi->clk_gals0); - if (ret) - goto err_disable_smi; - - ret = clk_prepare_enable(smi->clk_gals1); - if (ret) - goto err_disable_gals0; - - return 0; - -err_disable_gals0: - clk_disable_unprepare(smi->clk_gals0); -err_disable_smi: - clk_disable_unprepare(smi->clk_smi); -err_disable_apb: - clk_disable_unprepare(smi->clk_apb); - return ret; -} - -static void mtk_smi_clk_disable(const struct mtk_smi *smi) -{ - clk_disable_unprepare(smi->clk_gals1); - clk_disable_unprepare(smi->clk_gals0); - clk_disable_unprepare(smi->clk_smi); - clk_disable_unprepare(smi->clk_apb); -} - int mtk_smi_larb_get(struct device *larbdev) { int ret = pm_runtime_resume_and_get(larbdev); @@ -166,36 +181,16 @@ mtk_smi_larb_bind(struct device *dev, struct device *master, void *data) return -ENODEV; } -static void mtk_smi_larb_config_port_gen2_general(struct device *dev) -{ - struct mtk_smi_larb *larb = dev_get_drvdata(dev); - u32 reg; - int i; - - if (BIT(larb->larbid) & larb->larb_gen->larb_direct_to_common_mask) - return; - - for_each_set_bit(i, (unsigned long *)larb->mmu, 32) { - reg = readl_relaxed(larb->base + SMI_LARB_NONSEC_CON(i)); - reg |= F_MMU_EN; - reg |= BANK_SEL(larb->bank[i]); - writel(reg, larb->base + SMI_LARB_NONSEC_CON(i)); - } -} - -static void mtk_smi_larb_config_port_mt8173(struct device *dev) +static void +mtk_smi_larb_unbind(struct device *dev, struct device *master, void *data) { - struct mtk_smi_larb *larb = dev_get_drvdata(dev); - - writel(*larb->mmu, larb->base + SMI_LARB_MMU_EN); + /* Do nothing as the iommu is always enabled. */ } -static void mtk_smi_larb_config_port_mt8167(struct device *dev) -{ - struct mtk_smi_larb *larb = dev_get_drvdata(dev); - - writel(*larb->mmu, larb->base + MT8167_SMI_LARB_MMU_EN); -} +static const struct component_ops mtk_smi_larb_component_ops = { + .bind = mtk_smi_larb_bind, + .unbind = mtk_smi_larb_unbind, +}; static void mtk_smi_larb_config_port_gen1(struct device *dev) { @@ -228,25 +223,94 @@ static void mtk_smi_larb_config_port_gen1(struct device *dev) } } -static void -mtk_smi_larb_unbind(struct device *dev, struct device *master, void *data) +static void mtk_smi_larb_config_port_mt8167(struct device *dev) { - /* Do nothing as the iommu is always enabled. */ + struct mtk_smi_larb *larb = dev_get_drvdata(dev); + + writel(*larb->mmu, larb->base + MT8167_SMI_LARB_MMU_EN); } -static const struct component_ops mtk_smi_larb_component_ops = { - .bind = mtk_smi_larb_bind, - .unbind = mtk_smi_larb_unbind, -}; +static void mtk_smi_larb_config_port_mt8173(struct device *dev) +{ + struct mtk_smi_larb *larb = dev_get_drvdata(dev); -static const struct mtk_smi_larb_gen mtk_smi_larb_mt8173 = { - /* mt8173 do not need the port in larb */ - .config_port = mtk_smi_larb_config_port_mt8173, -}; + writel(*larb->mmu, larb->base + MT8173_SMI_LARB_MMU_EN); +} -static const struct mtk_smi_larb_gen mtk_smi_larb_mt8167 = { - /* mt8167 do not need the port in larb */ - .config_port = mtk_smi_larb_config_port_mt8167, +static void mtk_smi_larb_config_port_gen2_general(struct device *dev) +{ + struct mtk_smi_larb *larb = dev_get_drvdata(dev); + u32 reg, flags_general = larb->larb_gen->flags_general; + const u8 *larbostd = larb->larb_gen->ostd[larb->larbid]; + int i; + + if (BIT(larb->larbid) & larb->larb_gen->larb_direct_to_common_mask) + return; + + if (MTK_SMI_CAPS(flags_general, MTK_SMI_FLAG_THRT_UPDATE)) { + reg = readl_relaxed(larb->base + SMI_LARB_CMD_THRT_CON); + reg &= ~SMI_LARB_THRT_RD_NU_LMT_MSK; + reg |= SMI_LARB_THRT_RD_NU_LMT; + writel_relaxed(reg, larb->base + SMI_LARB_CMD_THRT_CON); + } + + if (MTK_SMI_CAPS(flags_general, MTK_SMI_FLAG_SW_FLAG)) + writel_relaxed(SMI_LARB_SW_FLAG_1, larb->base + SMI_LARB_SW_FLAG); + + for (i = 0; i < SMI_LARB_PORT_NR_MAX && larbostd && !!larbostd[i]; i++) + writel_relaxed(larbostd[i], larb->base + SMI_LARB_OSTDL_PORTx(i)); + + for_each_set_bit(i, (unsigned long *)larb->mmu, 32) { + reg = readl_relaxed(larb->base + SMI_LARB_NONSEC_CON(i)); + reg |= F_MMU_EN; + reg |= BANK_SEL(larb->bank[i]); + writel(reg, larb->base + SMI_LARB_NONSEC_CON(i)); + } +} + +static const u8 mtk_smi_larb_mt8195_ostd[][SMI_LARB_PORT_NR_MAX] = { + [0] = {0x0a, 0xc, 0x22, 0x22, 0x01, 0x0a,}, /* larb0 */ + [1] = {0x0a, 0xc, 0x22, 0x22, 0x01, 0x0a,}, /* larb1 */ + [2] = {0x12, 0x12, 0x12, 0x12, 0x0a,}, /* ... */ + [3] = {0x12, 0x12, 0x12, 0x12, 0x28, 0x28, 0x0a,}, + [4] = {0x06, 0x01, 0x17, 0x06, 0x0a,}, + [5] = {0x06, 0x01, 0x17, 0x06, 0x06, 0x01, 0x06, 0x0a,}, + [6] = {0x06, 0x01, 0x06, 0x0a,}, + [7] = {0x0c, 0x0c, 0x12,}, + [8] = {0x0c, 0x0c, 0x12,}, + [9] = {0x0a, 0x08, 0x04, 0x06, 0x01, 0x01, 0x10, 0x18, 0x11, 0x0a, + 0x08, 0x04, 0x11, 0x06, 0x02, 0x06, 0x01, 0x11, 0x11, 0x06,}, + [10] = {0x18, 0x08, 0x01, 0x01, 0x20, 0x12, 0x18, 0x06, 0x05, 0x10, + 0x08, 0x08, 0x10, 0x08, 0x08, 0x18, 0x0c, 0x09, 0x0b, 0x0d, + 0x0d, 0x06, 0x10, 0x10,}, + [11] = {0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x01, 0x01, 0x01, 0x01,}, + [12] = {0x09, 0x09, 0x05, 0x05, 0x0c, 0x18, 0x02, 0x02, 0x04, 0x02,}, + [13] = {0x02, 0x02, 0x12, 0x12, 0x02, 0x02, 0x02, 0x02, 0x08, 0x01,}, + [14] = {0x12, 0x12, 0x02, 0x02, 0x02, 0x02, 0x16, 0x01, 0x16, 0x01, + 0x01, 0x02, 0x02, 0x08, 0x02,}, + [15] = {}, + [16] = {0x28, 0x02, 0x02, 0x12, 0x02, 0x12, 0x10, 0x02, 0x02, 0x0a, + 0x12, 0x02, 0x0a, 0x16, 0x02, 0x04,}, + [17] = {0x1a, 0x0e, 0x0a, 0x0a, 0x0c, 0x0e, 0x10,}, + [18] = {0x12, 0x06, 0x12, 0x06,}, + [19] = {0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x01, + 0x01, 0x01, 0x04, 0x0a, 0x06, 0x01, 0x01, 0x01, 0x0a, 0x06, + 0x01, 0x01, 0x05, 0x03, 0x03, 0x04, 0x01,}, + [20] = {0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x01, + 0x01, 0x01, 0x04, 0x0a, 0x06, 0x01, 0x01, 0x01, 0x0a, 0x06, + 0x01, 0x01, 0x05, 0x03, 0x03, 0x04, 0x01,}, + [21] = {0x28, 0x19, 0x0c, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04,}, + [22] = {0x28, 0x19, 0x0c, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04,}, + [23] = {0x18, 0x01,}, + [24] = {0x01, 0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x01, + 0x01, 0x01,}, + [25] = {0x02, 0x02, 0x02, 0x28, 0x16, 0x02, 0x02, 0x02, 0x12, 0x16, + 0x02, 0x01,}, + [26] = {0x02, 0x02, 0x02, 0x28, 0x16, 0x02, 0x02, 0x02, 0x12, 0x16, + 0x02, 0x01,}, + [27] = {0x02, 0x02, 0x02, 0x28, 0x16, 0x02, 0x02, 0x02, 0x12, 0x16, + 0x02, 0x01,}, + [28] = {0x1a, 0x0e, 0x0a, 0x0a, 0x0c, 0x0e, 0x10,}, }; static const struct mtk_smi_larb_gen mtk_smi_larb_mt2701 = { @@ -269,8 +333,17 @@ static const struct mtk_smi_larb_gen mtk_smi_larb_mt6779 = { /* DUMMY | IPU0 | IPU1 | CCU | MDLA */ }; +static const struct mtk_smi_larb_gen mtk_smi_larb_mt8167 = { + /* mt8167 do not need the port in larb */ + .config_port = mtk_smi_larb_config_port_mt8167, +}; + +static const struct mtk_smi_larb_gen mtk_smi_larb_mt8173 = { + /* mt8173 do not need the port in larb */ + .config_port = mtk_smi_larb_config_port_mt8173, +}; + static const struct mtk_smi_larb_gen mtk_smi_larb_mt8183 = { - .has_gals = true, .config_port = mtk_smi_larb_config_port_gen2_general, .larb_direct_to_common_mask = BIT(2) | BIT(3) | BIT(7), /* IPU0 | IPU1 | CCU */ @@ -280,99 +353,114 @@ static const struct mtk_smi_larb_gen mtk_smi_larb_mt8192 = { .config_port = mtk_smi_larb_config_port_gen2_general, }; +static const struct mtk_smi_larb_gen mtk_smi_larb_mt8195 = { + .config_port = mtk_smi_larb_config_port_gen2_general, + .flags_general = MTK_SMI_FLAG_THRT_UPDATE | MTK_SMI_FLAG_SW_FLAG, + .ostd = mtk_smi_larb_mt8195_ostd, +}; + static const struct of_device_id mtk_smi_larb_of_ids[] = { - { - .compatible = "mediatek,mt8167-smi-larb", - .data = &mtk_smi_larb_mt8167 - }, - { - .compatible = "mediatek,mt8173-smi-larb", - .data = &mtk_smi_larb_mt8173 - }, - { - .compatible = "mediatek,mt2701-smi-larb", - .data = &mtk_smi_larb_mt2701 - }, - { - .compatible = "mediatek,mt2712-smi-larb", - .data = &mtk_smi_larb_mt2712 - }, - { - .compatible = "mediatek,mt6779-smi-larb", - .data = &mtk_smi_larb_mt6779 - }, - { - .compatible = "mediatek,mt8183-smi-larb", - .data = &mtk_smi_larb_mt8183 - }, - { - .compatible = "mediatek,mt8192-smi-larb", - .data = &mtk_smi_larb_mt8192 - }, + {.compatible = "mediatek,mt2701-smi-larb", .data = &mtk_smi_larb_mt2701}, + {.compatible = "mediatek,mt2712-smi-larb", .data = &mtk_smi_larb_mt2712}, + {.compatible = "mediatek,mt6779-smi-larb", .data = &mtk_smi_larb_mt6779}, + {.compatible = "mediatek,mt8167-smi-larb", .data = &mtk_smi_larb_mt8167}, + {.compatible = "mediatek,mt8173-smi-larb", .data = &mtk_smi_larb_mt8173}, + {.compatible = "mediatek,mt8183-smi-larb", .data = &mtk_smi_larb_mt8183}, + {.compatible = "mediatek,mt8192-smi-larb", .data = &mtk_smi_larb_mt8192}, + {.compatible = "mediatek,mt8195-smi-larb", .data = &mtk_smi_larb_mt8195}, {} }; -static int mtk_smi_larb_probe(struct platform_device *pdev) +static int mtk_smi_device_link_common(struct device *dev, struct device **com_dev) { - struct mtk_smi_larb *larb; - struct resource *res; - struct device *dev = &pdev->dev; - struct device_node *smi_node; - struct platform_device *smi_pdev; + struct platform_device *smi_com_pdev; + struct device_node *smi_com_node; + struct device *smi_com_dev; struct device_link *link; - larb = devm_kzalloc(dev, sizeof(*larb), GFP_KERNEL); - if (!larb) - return -ENOMEM; - - larb->larb_gen = of_device_get_match_data(dev); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - larb->base = devm_ioremap_resource(dev, res); - if (IS_ERR(larb->base)) - return PTR_ERR(larb->base); - - larb->smi.clk_apb = devm_clk_get(dev, "apb"); - if (IS_ERR(larb->smi.clk_apb)) - return PTR_ERR(larb->smi.clk_apb); - - larb->smi.clk_smi = devm_clk_get(dev, "smi"); - if (IS_ERR(larb->smi.clk_smi)) - return PTR_ERR(larb->smi.clk_smi); - - if (larb->larb_gen->has_gals) { - /* The larbs may still haven't gals even if the SoC support.*/ - larb->smi.clk_gals0 = devm_clk_get(dev, "gals"); - if (PTR_ERR(larb->smi.clk_gals0) == -ENOENT) - larb->smi.clk_gals0 = NULL; - else if (IS_ERR(larb->smi.clk_gals0)) - return PTR_ERR(larb->smi.clk_gals0); - } - larb->smi.dev = dev; - - smi_node = of_parse_phandle(dev->of_node, "mediatek,smi", 0); - if (!smi_node) + smi_com_node = of_parse_phandle(dev->of_node, "mediatek,smi", 0); + if (!smi_com_node) return -EINVAL; - smi_pdev = of_find_device_by_node(smi_node); - of_node_put(smi_node); - if (smi_pdev) { - if (!platform_get_drvdata(smi_pdev)) + smi_com_pdev = of_find_device_by_node(smi_com_node); + of_node_put(smi_com_node); + if (smi_com_pdev) { + /* smi common is the supplier, Make sure it is ready before */ + if (!platform_get_drvdata(smi_com_pdev)) return -EPROBE_DEFER; - larb->smi_common_dev = &smi_pdev->dev; - link = device_link_add(dev, larb->smi_common_dev, + smi_com_dev = &smi_com_pdev->dev; + link = device_link_add(dev, smi_com_dev, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS); if (!link) { dev_err(dev, "Unable to link smi-common dev\n"); return -ENODEV; } + *com_dev = smi_com_dev; } else { dev_err(dev, "Failed to get the smi_common device\n"); return -EINVAL; } + return 0; +} + +static int mtk_smi_dts_clk_init(struct device *dev, struct mtk_smi *smi, + const char * const clks[], + unsigned int clk_nr_required, + unsigned int clk_nr_optional) +{ + int i, ret; + + for (i = 0; i < clk_nr_required; i++) + smi->clks[i].id = clks[i]; + ret = devm_clk_bulk_get(dev, clk_nr_required, smi->clks); + if (ret) + return ret; + + for (i = clk_nr_required; i < clk_nr_required + clk_nr_optional; i++) + smi->clks[i].id = clks[i]; + ret = devm_clk_bulk_get_optional(dev, clk_nr_optional, + smi->clks + clk_nr_required); + smi->clk_num = clk_nr_required + clk_nr_optional; + return ret; +} + +static int mtk_smi_larb_probe(struct platform_device *pdev) +{ + struct mtk_smi_larb *larb; + struct device *dev = &pdev->dev; + int ret; + + larb = devm_kzalloc(dev, sizeof(*larb), GFP_KERNEL); + if (!larb) + return -ENOMEM; + + larb->larb_gen = of_device_get_match_data(dev); + larb->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(larb->base)) + return PTR_ERR(larb->base); + + ret = mtk_smi_dts_clk_init(dev, &larb->smi, mtk_smi_larb_clks, + MTK_SMI_LARB_REQ_CLK_NR, MTK_SMI_LARB_OPT_CLK_NR); + if (ret) + return ret; + + larb->smi.dev = dev; + + ret = mtk_smi_device_link_common(dev, &larb->smi_common_dev); + if (ret < 0) + return ret; pm_runtime_enable(dev); platform_set_drvdata(pdev, larb); - return component_add(dev, &mtk_smi_larb_component_ops); + ret = component_add(dev, &mtk_smi_larb_component_ops); + if (ret) + goto err_pm_disable; + return 0; + +err_pm_disable: + pm_runtime_disable(dev); + device_link_remove(dev, larb->smi_common_dev); + return ret; } static int mtk_smi_larb_remove(struct platform_device *pdev) @@ -391,11 +479,9 @@ static int __maybe_unused mtk_smi_larb_resume(struct device *dev) const struct mtk_smi_larb_gen *larb_gen = larb->larb_gen; int ret; - ret = mtk_smi_clk_enable(&larb->smi); - if (ret < 0) { - dev_err(dev, "Failed to enable clock(%d).\n", ret); + ret = clk_bulk_prepare_enable(larb->smi.clk_num, larb->smi.clks); + if (ret < 0) return ret; - } /* Configure the basic setting for this larb */ larb_gen->config_port(dev); @@ -407,7 +493,7 @@ static int __maybe_unused mtk_smi_larb_suspend(struct device *dev) { struct mtk_smi_larb *larb = dev_get_drvdata(dev); - mtk_smi_clk_disable(&larb->smi); + clk_bulk_disable_unprepare(larb->smi.clk_num, larb->smi.clks); return 0; } @@ -427,64 +513,75 @@ static struct platform_driver mtk_smi_larb_driver = { } }; +static const struct mtk_smi_reg_pair mtk_smi_common_mt8195_init[SMI_COMMON_INIT_REGS_NR] = { + {SMI_L1LEN, 0xb}, + {SMI_M4U_TH, 0xe100e10}, + {SMI_FIFO_TH1, 0x506090a}, + {SMI_FIFO_TH2, 0x506090a}, + {SMI_DCM, 0x4f1}, + {SMI_DUMMY, 0x1}, +}; + static const struct mtk_smi_common_plat mtk_smi_common_gen1 = { - .gen = MTK_SMI_GEN1, + .type = MTK_SMI_GEN1, }; static const struct mtk_smi_common_plat mtk_smi_common_gen2 = { - .gen = MTK_SMI_GEN2, + .type = MTK_SMI_GEN2, }; static const struct mtk_smi_common_plat mtk_smi_common_mt6779 = { - .gen = MTK_SMI_GEN2, - .has_gals = true, - .bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(4) | - F_MMU1_LARB(5) | F_MMU1_LARB(6) | F_MMU1_LARB(7), + .type = MTK_SMI_GEN2, + .has_gals = true, + .bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(4) | + F_MMU1_LARB(5) | F_MMU1_LARB(6) | F_MMU1_LARB(7), }; static const struct mtk_smi_common_plat mtk_smi_common_mt8183 = { - .gen = MTK_SMI_GEN2, + .type = MTK_SMI_GEN2, .has_gals = true, .bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(5) | F_MMU1_LARB(7), }; static const struct mtk_smi_common_plat mtk_smi_common_mt8192 = { - .gen = MTK_SMI_GEN2, + .type = MTK_SMI_GEN2, .has_gals = true, .bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(5) | F_MMU1_LARB(6), }; +static const struct mtk_smi_common_plat mtk_smi_common_mt8195_vdo = { + .type = MTK_SMI_GEN2, + .has_gals = true, + .bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(3) | F_MMU1_LARB(5) | + F_MMU1_LARB(7), + .init = mtk_smi_common_mt8195_init, +}; + +static const struct mtk_smi_common_plat mtk_smi_common_mt8195_vpp = { + .type = MTK_SMI_GEN2, + .has_gals = true, + .bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(7), + .init = mtk_smi_common_mt8195_init, +}; + +static const struct mtk_smi_common_plat mtk_smi_sub_common_mt8195 = { + .type = MTK_SMI_GEN2_SUB_COMM, + .has_gals = true, +}; + static const struct of_device_id mtk_smi_common_of_ids[] = { - { - .compatible = "mediatek,mt8173-smi-common", - .data = &mtk_smi_common_gen2, - }, - { - .compatible = "mediatek,mt8167-smi-common", - .data = &mtk_smi_common_gen2, - }, - { - .compatible = "mediatek,mt2701-smi-common", - .data = &mtk_smi_common_gen1, - }, - { - .compatible = "mediatek,mt2712-smi-common", - .data = &mtk_smi_common_gen2, - }, - { - .compatible = "mediatek,mt6779-smi-common", - .data = &mtk_smi_common_mt6779, - }, - { - .compatible = "mediatek,mt8183-smi-common", - .data = &mtk_smi_common_mt8183, - }, - { - .compatible = "mediatek,mt8192-smi-common", - .data = &mtk_smi_common_mt8192, - }, + {.compatible = "mediatek,mt2701-smi-common", .data = &mtk_smi_common_gen1}, + {.compatible = "mediatek,mt2712-smi-common", .data = &mtk_smi_common_gen2}, + {.compatible = "mediatek,mt6779-smi-common", .data = &mtk_smi_common_mt6779}, + {.compatible = "mediatek,mt8167-smi-common", .data = &mtk_smi_common_gen2}, + {.compatible = "mediatek,mt8173-smi-common", .data = &mtk_smi_common_gen2}, + {.compatible = "mediatek,mt8183-smi-common", .data = &mtk_smi_common_mt8183}, + {.compatible = "mediatek,mt8192-smi-common", .data = &mtk_smi_common_mt8192}, + {.compatible = "mediatek,mt8195-smi-common-vdo", .data = &mtk_smi_common_mt8195_vdo}, + {.compatible = "mediatek,mt8195-smi-common-vpp", .data = &mtk_smi_common_mt8195_vpp}, + {.compatible = "mediatek,mt8195-smi-sub-common", .data = &mtk_smi_sub_common_mt8195}, {} }; @@ -492,8 +589,7 @@ static int mtk_smi_common_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk_smi *common; - struct resource *res; - int ret; + int ret, clk_required = MTK_SMI_COM_REQ_CLK_NR; common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL); if (!common) @@ -501,23 +597,15 @@ static int mtk_smi_common_probe(struct platform_device *pdev) common->dev = dev; common->plat = of_device_get_match_data(dev); - common->clk_apb = devm_clk_get(dev, "apb"); - if (IS_ERR(common->clk_apb)) - return PTR_ERR(common->clk_apb); - - common->clk_smi = devm_clk_get(dev, "smi"); - if (IS_ERR(common->clk_smi)) - return PTR_ERR(common->clk_smi); - if (common->plat->has_gals) { - common->clk_gals0 = devm_clk_get(dev, "gals0"); - if (IS_ERR(common->clk_gals0)) - return PTR_ERR(common->clk_gals0); - - common->clk_gals1 = devm_clk_get(dev, "gals1"); - if (IS_ERR(common->clk_gals1)) - return PTR_ERR(common->clk_gals1); + if (common->plat->type == MTK_SMI_GEN2) + clk_required = MTK_SMI_COM_GALS_REQ_CLK_NR; + else if (common->plat->type == MTK_SMI_GEN2_SUB_COMM) + clk_required = MTK_SMI_SUB_COM_GALS_REQ_CLK_NR; } + ret = mtk_smi_dts_clk_init(dev, common, mtk_smi_common_clks, clk_required, 0); + if (ret) + return ret; /* * for mtk smi gen 1, we need to get the ao(always on) base to config @@ -525,9 +613,8 @@ static int mtk_smi_common_probe(struct platform_device *pdev) * clock into emi clock domain, but for mtk smi gen2, there's no smi ao * base. */ - if (common->plat->gen == MTK_SMI_GEN1) { - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - common->smi_ao_base = devm_ioremap_resource(dev, res); + if (common->plat->type == MTK_SMI_GEN1) { + common->smi_ao_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(common->smi_ao_base)) return PTR_ERR(common->smi_ao_base); @@ -539,11 +626,18 @@ static int mtk_smi_common_probe(struct platform_device *pdev) if (ret) return ret; } else { - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - common->base = devm_ioremap_resource(dev, res); + common->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(common->base)) return PTR_ERR(common->base); } + + /* link its smi-common if this is smi-sub-common */ + if (common->plat->type == MTK_SMI_GEN2_SUB_COMM) { + ret = mtk_smi_device_link_common(dev, &common->smi_common_dev); + if (ret < 0) + return ret; + } + pm_runtime_enable(dev); platform_set_drvdata(pdev, common); return 0; @@ -551,6 +645,10 @@ static int mtk_smi_common_probe(struct platform_device *pdev) static int mtk_smi_common_remove(struct platform_device *pdev) { + struct mtk_smi *common = dev_get_drvdata(&pdev->dev); + + if (common->plat->type == MTK_SMI_GEN2_SUB_COMM) + device_link_remove(&pdev->dev, common->smi_common_dev); pm_runtime_disable(&pdev->dev); return 0; } @@ -558,17 +656,21 @@ static int mtk_smi_common_remove(struct platform_device *pdev) static int __maybe_unused mtk_smi_common_resume(struct device *dev) { struct mtk_smi *common = dev_get_drvdata(dev); - u32 bus_sel = common->plat->bus_sel; - int ret; + const struct mtk_smi_reg_pair *init = common->plat->init; + u32 bus_sel = common->plat->bus_sel; /* default is 0 */ + int ret, i; - ret = mtk_smi_clk_enable(common); - if (ret) { - dev_err(common->dev, "Failed to enable clock(%d).\n", ret); + ret = clk_bulk_prepare_enable(common->clk_num, common->clks); + if (ret) return ret; - } - if (common->plat->gen == MTK_SMI_GEN2 && bus_sel) - writel(bus_sel, common->base + SMI_BUS_SEL); + if (common->plat->type != MTK_SMI_GEN2) + return 0; + + for (i = 0; i < SMI_COMMON_INIT_REGS_NR && init && init[i].offset; i++) + writel_relaxed(init[i].value, common->base + init[i].offset); + + writel(bus_sel, common->base + SMI_BUS_SEL); return 0; } @@ -576,7 +678,7 @@ static int __maybe_unused mtk_smi_common_suspend(struct device *dev) { struct mtk_smi *common = dev_get_drvdata(dev); - mtk_smi_clk_disable(common); + clk_bulk_disable_unprepare(common->clk_num, common->clks); return 0; } diff --git a/drivers/memory/of_memory.c b/drivers/memory/of_memory.c index d9f5437d3bce..b94408954d85 100644 --- a/drivers/memory/of_memory.c +++ b/drivers/memory/of_memory.c @@ -298,3 +298,90 @@ default_timings: return NULL; } EXPORT_SYMBOL(of_lpddr3_get_ddr_timings); + +/** + * of_lpddr2_get_info() - extracts information about the lpddr2 chip. + * @np: Pointer to device tree node containing lpddr2 info + * @dev: Device requesting info + * + * Populates lpddr2_info structure by extracting data from device + * tree node. Returns pointer to populated structure. If error + * happened while populating, returns NULL. If property is missing + * in a device-tree, then the corresponding value is set to -ENOENT. + */ +const struct lpddr2_info +*of_lpddr2_get_info(struct device_node *np, struct device *dev) +{ + struct lpddr2_info *ret_info, info = {}; + struct property *prop; + const char *cp; + int err; + + err = of_property_read_u32(np, "revision-id1", &info.revision_id1); + if (err) + info.revision_id1 = -ENOENT; + + err = of_property_read_u32(np, "revision-id2", &info.revision_id2); + if (err) + info.revision_id2 = -ENOENT; + + err = of_property_read_u32(np, "io-width", &info.io_width); + if (err) + return NULL; + + info.io_width = 32 / info.io_width - 1; + + err = of_property_read_u32(np, "density", &info.density); + if (err) + return NULL; + + info.density = ffs(info.density) - 7; + + if (of_device_is_compatible(np, "jedec,lpddr2-s4")) + info.arch_type = LPDDR2_TYPE_S4; + else if (of_device_is_compatible(np, "jedec,lpddr2-s2")) + info.arch_type = LPDDR2_TYPE_S2; + else if (of_device_is_compatible(np, "jedec,lpddr2-nvm")) + info.arch_type = LPDDR2_TYPE_NVM; + else + return NULL; + + prop = of_find_property(np, "compatible", NULL); + for (cp = of_prop_next_string(prop, NULL); cp; + cp = of_prop_next_string(prop, cp)) { + +#define OF_LPDDR2_VENDOR_CMP(compat, ID) \ + if (!of_compat_cmp(cp, compat ",", strlen(compat ","))) { \ + info.manufacturer_id = LPDDR2_MANID_##ID; \ + break; \ + } + + OF_LPDDR2_VENDOR_CMP("samsung", SAMSUNG) + OF_LPDDR2_VENDOR_CMP("qimonda", QIMONDA) + OF_LPDDR2_VENDOR_CMP("elpida", ELPIDA) + OF_LPDDR2_VENDOR_CMP("etron", ETRON) + OF_LPDDR2_VENDOR_CMP("nanya", NANYA) + OF_LPDDR2_VENDOR_CMP("hynix", HYNIX) + OF_LPDDR2_VENDOR_CMP("mosel", MOSEL) + OF_LPDDR2_VENDOR_CMP("winbond", WINBOND) + OF_LPDDR2_VENDOR_CMP("esmt", ESMT) + OF_LPDDR2_VENDOR_CMP("spansion", SPANSION) + OF_LPDDR2_VENDOR_CMP("sst", SST) + OF_LPDDR2_VENDOR_CMP("zmos", ZMOS) + OF_LPDDR2_VENDOR_CMP("intel", INTEL) + OF_LPDDR2_VENDOR_CMP("numonyx", NUMONYX) + OF_LPDDR2_VENDOR_CMP("micron", MICRON) + +#undef OF_LPDDR2_VENDOR_CMP + } + + if (!info.manufacturer_id) + info.manufacturer_id = -ENOENT; + + ret_info = devm_kzalloc(dev, sizeof(*ret_info), GFP_KERNEL); + if (ret_info) + *ret_info = info; + + return ret_info; +} +EXPORT_SYMBOL(of_lpddr2_get_info); diff --git a/drivers/memory/of_memory.h b/drivers/memory/of_memory.h index 4a99b232ab0a..1c4e47fede8a 100644 --- a/drivers/memory/of_memory.h +++ b/drivers/memory/of_memory.h @@ -20,6 +20,9 @@ const struct lpddr3_min_tck *of_lpddr3_get_min_tck(struct device_node *np, const struct lpddr3_timings * of_lpddr3_get_ddr_timings(struct device_node *np_ddr, struct device *dev, u32 device_type, u32 *nr_frequencies); + +const struct lpddr2_info *of_lpddr2_get_info(struct device_node *np, + struct device *dev); #else static inline const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np, struct device *dev) @@ -46,6 +49,12 @@ static inline const struct lpddr3_timings { return NULL; } + +static inline const struct lpddr2_info + *of_lpddr2_get_info(struct device_node *np, struct device *dev) +{ + return NULL; +} #endif /* CONFIG_OF && CONFIG_DDR */ #endif /* __LINUX_MEMORY_OF_REG_ */ diff --git a/drivers/memory/renesas-rpc-if.c b/drivers/memory/renesas-rpc-if.c index 45eed659b0c6..7435baad0007 100644 --- a/drivers/memory/renesas-rpc-if.c +++ b/drivers/memory/renesas-rpc-if.c @@ -160,10 +160,61 @@ static const struct regmap_access_table rpcif_volatile_table = { .n_yes_ranges = ARRAY_SIZE(rpcif_volatile_ranges), }; + +/* + * Custom accessor functions to ensure SMRDR0 and SMWDR0 are always accessed + * with proper width. Requires SMENR_SPIDE to be correctly set before! + */ +static int rpcif_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct rpcif *rpc = context; + + if (reg == RPCIF_SMRDR0 || reg == RPCIF_SMWDR0) { + u32 spide = readl(rpc->base + RPCIF_SMENR) & RPCIF_SMENR_SPIDE(0xF); + + if (spide == 0x8) { + *val = readb(rpc->base + reg); + return 0; + } else if (spide == 0xC) { + *val = readw(rpc->base + reg); + return 0; + } else if (spide != 0xF) { + return -EILSEQ; + } + } + + *val = readl(rpc->base + reg); + return 0; +} + +static int rpcif_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct rpcif *rpc = context; + + if (reg == RPCIF_SMRDR0 || reg == RPCIF_SMWDR0) { + u32 spide = readl(rpc->base + RPCIF_SMENR) & RPCIF_SMENR_SPIDE(0xF); + + if (spide == 0x8) { + writeb(val, rpc->base + reg); + return 0; + } else if (spide == 0xC) { + writew(val, rpc->base + reg); + return 0; + } else if (spide != 0xF) { + return -EILSEQ; + } + } + + writel(val, rpc->base + reg); + return 0; +} + static const struct regmap_config rpcif_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, + .reg_read = rpcif_reg_read, + .reg_write = rpcif_reg_write, .fast_io = true, .max_register = RPCIF_PHYINT, .volatile_table = &rpcif_volatile_table, @@ -173,17 +224,15 @@ int rpcif_sw_init(struct rpcif *rpc, struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct resource *res; - void __iomem *base; rpc->dev = dev; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); - base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); + rpc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rpc->base)) + return PTR_ERR(rpc->base); - rpc->regmap = devm_regmap_init_mmio(&pdev->dev, base, - &rpcif_regmap_config); + rpc->regmap = devm_regmap_init(&pdev->dev, NULL, rpc, &rpcif_regmap_config); if (IS_ERR(rpc->regmap)) { dev_err(&pdev->dev, "failed to init regmap for rpcif, error %ld\n", @@ -354,20 +403,16 @@ void rpcif_prepare(struct rpcif *rpc, const struct rpcif_op *op, u64 *offs, nbytes = op->data.nbytes; rpc->xferlen = nbytes; - rpc->enable |= RPCIF_SMENR_SPIDE(rpcif_bits_set(rpc, nbytes)) | - RPCIF_SMENR_SPIDB(rpcif_bit_size(op->data.buswidth)); + rpc->enable |= RPCIF_SMENR_SPIDB(rpcif_bit_size(op->data.buswidth)); } } EXPORT_SYMBOL(rpcif_prepare); int rpcif_manual_xfer(struct rpcif *rpc) { - u32 smenr, smcr, pos = 0, max = 4; + u32 smenr, smcr, pos = 0, max = rpc->bus_size == 2 ? 8 : 4; int ret = 0; - if (rpc->bus_size == 2) - max = 8; - pm_runtime_get_sync(rpc->dev); regmap_update_bits(rpc->regmap, RPCIF_PHYCNT, @@ -378,37 +423,36 @@ int rpcif_manual_xfer(struct rpcif *rpc) regmap_write(rpc->regmap, RPCIF_SMOPR, rpc->option); regmap_write(rpc->regmap, RPCIF_SMDMCR, rpc->dummy); regmap_write(rpc->regmap, RPCIF_SMDRENR, rpc->ddr); + regmap_write(rpc->regmap, RPCIF_SMADR, rpc->smadr); smenr = rpc->enable; switch (rpc->dir) { case RPCIF_DATA_OUT: while (pos < rpc->xferlen) { - u32 nbytes = rpc->xferlen - pos; - u32 data[2]; + u32 bytes_left = rpc->xferlen - pos; + u32 nbytes, data[2]; smcr = rpc->smcr | RPCIF_SMCR_SPIE; - if (nbytes > max) { - nbytes = max; + + /* nbytes may only be 1, 2, 4, or 8 */ + nbytes = bytes_left >= max ? max : (1 << ilog2(bytes_left)); + if (bytes_left > nbytes) smcr |= RPCIF_SMCR_SSLKP; - } + + smenr |= RPCIF_SMENR_SPIDE(rpcif_bits_set(rpc, nbytes)); + regmap_write(rpc->regmap, RPCIF_SMENR, smenr); memcpy(data, rpc->buffer + pos, nbytes); - if (nbytes > 4) { + if (nbytes == 8) { regmap_write(rpc->regmap, RPCIF_SMWDR1, data[0]); regmap_write(rpc->regmap, RPCIF_SMWDR0, data[1]); - } else if (nbytes > 2) { + } else { regmap_write(rpc->regmap, RPCIF_SMWDR0, data[0]); - } else { - regmap_write(rpc->regmap, RPCIF_SMWDR0, - data[0] << 16); } - regmap_write(rpc->regmap, RPCIF_SMADR, - rpc->smadr + pos); - regmap_write(rpc->regmap, RPCIF_SMENR, smenr); regmap_write(rpc->regmap, RPCIF_SMCR, smcr); ret = wait_msg_xfer_end(rpc); if (ret) @@ -448,14 +492,16 @@ int rpcif_manual_xfer(struct rpcif *rpc) break; } while (pos < rpc->xferlen) { - u32 nbytes = rpc->xferlen - pos; - u32 data[2]; + u32 bytes_left = rpc->xferlen - pos; + u32 nbytes, data[2]; - if (nbytes > max) - nbytes = max; + /* nbytes may only be 1, 2, 4, or 8 */ + nbytes = bytes_left >= max ? max : (1 << ilog2(bytes_left)); regmap_write(rpc->regmap, RPCIF_SMADR, rpc->smadr + pos); + smenr &= ~RPCIF_SMENR_SPIDE(0xF); + smenr |= RPCIF_SMENR_SPIDE(rpcif_bits_set(rpc, nbytes)); regmap_write(rpc->regmap, RPCIF_SMENR, smenr); regmap_write(rpc->regmap, RPCIF_SMCR, rpc->smcr | RPCIF_SMCR_SPIE); @@ -463,18 +509,14 @@ int rpcif_manual_xfer(struct rpcif *rpc) if (ret) goto err_out; - if (nbytes > 4) { + if (nbytes == 8) { regmap_read(rpc->regmap, RPCIF_SMRDR1, &data[0]); regmap_read(rpc->regmap, RPCIF_SMRDR0, &data[1]); - } else if (nbytes > 2) { - regmap_read(rpc->regmap, RPCIF_SMRDR0, - &data[0]); - } else { + } else { regmap_read(rpc->regmap, RPCIF_SMRDR0, &data[0]); - data[0] >>= 16; } memcpy(rpc->buffer + pos, data, nbytes); @@ -502,6 +544,48 @@ err_out: } EXPORT_SYMBOL(rpcif_manual_xfer); +static void memcpy_fromio_readw(void *to, + const void __iomem *from, + size_t count) +{ + const int maxw = (IS_ENABLED(CONFIG_64BIT)) ? 8 : 4; + u8 buf[2]; + + if (count && ((unsigned long)from & 1)) { + *(u16 *)buf = __raw_readw((void __iomem *)((unsigned long)from & ~1)); + *(u8 *)to = buf[1]; + from++; + to++; + count--; + } + while (count >= 2 && !IS_ALIGNED((unsigned long)from, maxw)) { + *(u16 *)to = __raw_readw(from); + from += 2; + to += 2; + count -= 2; + } + while (count >= maxw) { +#ifdef CONFIG_64BIT + *(u64 *)to = __raw_readq(from); +#else + *(u32 *)to = __raw_readl(from); +#endif + from += maxw; + to += maxw; + count -= maxw; + } + while (count >= 2) { + *(u16 *)to = __raw_readw(from); + from += 2; + to += 2; + count -= 2; + } + if (count) { + *(u16 *)buf = __raw_readw(from); + *(u8 *)to = buf[0]; + } +} + ssize_t rpcif_dirmap_read(struct rpcif *rpc, u64 offs, size_t len, void *buf) { loff_t from = offs & (RPCIF_DIRMAP_SIZE - 1); @@ -523,7 +607,10 @@ ssize_t rpcif_dirmap_read(struct rpcif *rpc, u64 offs, size_t len, void *buf) regmap_write(rpc->regmap, RPCIF_DRDMCR, rpc->dummy); regmap_write(rpc->regmap, RPCIF_DRDRENR, rpc->ddr); - memcpy_fromio(buf, rpc->dirmap + from, len); + if (rpc->bus_size == 2) + memcpy_fromio_readw(buf, rpc->dirmap + from, len); + else + memcpy_fromio(buf, rpc->dirmap + from, len); pm_runtime_put(rpc->dev); diff --git a/drivers/memory/samsung/Kconfig b/drivers/memory/samsung/Kconfig index 8e240f078afc..7fb70f573031 100644 --- a/drivers/memory/samsung/Kconfig +++ b/drivers/memory/samsung/Kconfig @@ -14,11 +14,12 @@ config EXYNOS5422_DMC depends on DEVFREQ_GOV_SIMPLE_ONDEMAND depends on (PM_DEVFREQ && PM_DEVFREQ_EVENT) help - This adds driver for Exynos5422 DMC (Dynamic Memory Controller). - The driver provides support for Dynamic Voltage and Frequency Scaling in - DMC and DRAM. It also supports changing timings of DRAM running with - different frequency. The timings are calculated based on DT memory - information. + This adds driver for Samsung Exynos5422 SoC DMC (Dynamic Memory + Controller). The driver provides support for Dynamic Voltage and + Frequency Scaling in DMC and DRAM. It also supports changing timings + of DRAM running with different frequency. The timings are calculated + based on DT memory information. + If unsure, say Y on devices with Samsung Exynos SoCs. config EXYNOS_SROM bool "Exynos SROM controller driver" if COMPILE_TEST @@ -29,6 +30,6 @@ config EXYNOS_SROM during suspend. If however appropriate device tree configuration is provided, the driver enables support for external memory or external devices. - If unsure, say Y on devices with Samsung Exynos SocS. + If unsure, say Y on devices with Samsung Exynos SoCs. endif diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig index f9bae36c03a3..7951764b4efe 100644 --- a/drivers/memory/tegra/Kconfig +++ b/drivers/memory/tegra/Kconfig @@ -16,6 +16,7 @@ config TEGRA20_EMC depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST select DEVFREQ_GOV_SIMPLE_ONDEMAND select PM_DEVFREQ + select DDR help This driver is for the External Memory Controller (EMC) found on Tegra20 chips. The EMC controls the external DRAM on the board. diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index 3c5aae7abf35..44b4a4080920 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -87,11 +87,9 @@ struct tegra_mc *devm_tegra_memory_controller_get(struct device *dev) return ERR_PTR(-EPROBE_DEFER); } - err = devm_add_action(dev, tegra_mc_devm_action_put_device, mc); - if (err) { - put_device(mc->dev); + err = devm_add_action_or_reset(dev, tegra_mc_devm_action_put_device, mc); + if (err) return ERR_PTR(err); - } return mc; } @@ -706,15 +704,6 @@ static int tegra_mc_interconnect_setup(struct tegra_mc *mc) goto remove_nodes; } - /* - * MC driver is registered too early, so early that generic driver - * syncing doesn't work for the MC. But it doesn't really matter - * since syncing works for the EMC drivers, hence we can sync the - * MC driver by ourselves and then EMC will complete syncing of - * the whole ICC state. - */ - icc_sync_state(mc->dev); - return 0; remove_nodes: @@ -835,6 +824,15 @@ static int __maybe_unused tegra_mc_resume(struct device *dev) return 0; } +static void tegra_mc_sync_state(struct device *dev) +{ + struct tegra_mc *mc = dev_get_drvdata(dev); + + /* check whether ICC provider is registered */ + if (mc->provider.dev == dev) + icc_sync_state(dev); +} + static const struct dev_pm_ops tegra_mc_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(tegra_mc_suspend, tegra_mc_resume) }; @@ -845,6 +843,7 @@ static struct platform_driver tegra_mc_driver = { .of_match_table = tegra_mc_of_match, .pm = &tegra_mc_pm_ops, .suppress_bind_attrs = true, + .sync_state = tegra_mc_sync_state, }, .prevent_deferred_probe = true, .probe = tegra_mc_probe, diff --git a/drivers/memory/tegra/tegra186-emc.c b/drivers/memory/tegra/tegra186-emc.c index d65e7c2a580b..746c4ef2c0af 100644 --- a/drivers/memory/tegra/tegra186-emc.c +++ b/drivers/memory/tegra/tegra186-emc.c @@ -197,6 +197,11 @@ static int tegra186_emc_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to EMC DVFS pairs: %d\n", err); goto put_bpmp; } + if (msg.rx.ret < 0) { + err = -EINVAL; + dev_err(&pdev->dev, "EMC DVFS MRQ failed: %d (BPMP error code)\n", msg.rx.ret); + goto put_bpmp; + } emc->debugfs.min_rate = ULONG_MAX; emc->debugfs.max_rate = 0; diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c index c3462dbc8c22..497b6edbf3ca 100644 --- a/drivers/memory/tegra/tegra20-emc.c +++ b/drivers/memory/tegra/tegra20-emc.c @@ -5,6 +5,7 @@ * Author: Dmitry Osipenko <digetx@gmail.com> */ +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/clk/tegra.h> #include <linux/debugfs.h> @@ -27,11 +28,15 @@ #include <soc/tegra/common.h> #include <soc/tegra/fuse.h> +#include "../jedec_ddr.h" +#include "../of_memory.h" + #include "mc.h" #define EMC_INTSTATUS 0x000 #define EMC_INTMASK 0x004 #define EMC_DBG 0x008 +#define EMC_ADR_CFG_0 0x010 #define EMC_TIMING_CONTROL 0x028 #define EMC_RC 0x02c #define EMC_RFC 0x030 @@ -68,6 +73,7 @@ #define EMC_QUSE_EXTRA 0x0ac #define EMC_ODT_WRITE 0x0b0 #define EMC_ODT_READ 0x0b4 +#define EMC_MRR 0x0ec #define EMC_FBIO_CFG5 0x104 #define EMC_FBIO_CFG6 0x114 #define EMC_STAT_CONTROL 0x160 @@ -94,6 +100,7 @@ #define EMC_REFRESH_OVERFLOW_INT BIT(3) #define EMC_CLKCHANGE_COMPLETE_INT BIT(4) +#define EMC_MRR_DIVLD_INT BIT(5) #define EMC_DBG_READ_MUX_ASSEMBLY BIT(0) #define EMC_DBG_WRITE_MUX_ACTIVE BIT(1) @@ -102,11 +109,25 @@ #define EMC_DBG_CFG_PRIORITY BIT(24) #define EMC_FBIO_CFG5_DRAM_WIDTH_X16 BIT(4) +#define EMC_FBIO_CFG5_DRAM_TYPE GENMASK(1, 0) + +#define EMC_MRR_DEV_SELECTN GENMASK(31, 30) +#define EMC_MRR_MRR_MA GENMASK(23, 16) +#define EMC_MRR_MRR_DATA GENMASK(15, 0) + +#define EMC_ADR_CFG_0_EMEM_NUMDEV GENMASK(25, 24) #define EMC_PWR_GATHER_CLEAR (1 << 8) #define EMC_PWR_GATHER_DISABLE (2 << 8) #define EMC_PWR_GATHER_ENABLE (3 << 8) +enum emc_dram_type { + DRAM_TYPE_RESERVED, + DRAM_TYPE_DDR1, + DRAM_TYPE_LPDDR2, + DRAM_TYPE_DDR2, +}; + static const u16 emc_timing_registers[] = { EMC_RC, EMC_RFC, @@ -201,6 +222,14 @@ struct tegra_emc { struct mutex rate_lock; struct devfreq_simple_ondemand_data ondemand_data; + + /* memory chip identity information */ + union lpddr2_basic_config4 basic_conf4; + unsigned int manufacturer_id; + unsigned int revision_id1; + unsigned int revision_id2; + + bool mrr_error; }; static irqreturn_t tegra_emc_isr(int irq, void *data) @@ -397,15 +426,19 @@ static int tegra_emc_load_timings_from_dt(struct tegra_emc *emc, if (!emc->timings) return -ENOMEM; - emc->num_timings = child_count; timing = emc->timings; for_each_child_of_node(node, child) { + if (of_node_name_eq(child, "lpddr2")) + continue; + err = load_one_timing_from_dt(emc, timing++, child); if (err) { of_node_put(child); return err; } + + emc->num_timings++; } sort(emc->timings, emc->num_timings, sizeof(*timing), cmp_timings, @@ -422,12 +455,18 @@ static int tegra_emc_load_timings_from_dt(struct tegra_emc *emc, } static struct device_node * -tegra_emc_find_node_by_ram_code(struct device *dev) +tegra_emc_find_node_by_ram_code(struct tegra_emc *emc) { + struct device *dev = emc->dev; struct device_node *np; u32 value, ram_code; int err; + if (emc->mrr_error) { + dev_warn(dev, "memory timings skipped due to MRR error\n"); + return NULL; + } + if (of_get_child_count(dev->of_node) == 0) { dev_info_once(dev, "device-tree doesn't have memory timings\n"); return NULL; @@ -442,8 +481,49 @@ tegra_emc_find_node_by_ram_code(struct device *dev) np = of_find_node_by_name(np, "emc-tables")) { err = of_property_read_u32(np, "nvidia,ram-code", &value); if (err || value != ram_code) { - of_node_put(np); - continue; + struct device_node *lpddr2_np; + bool cfg_mismatches = false; + + lpddr2_np = of_find_node_by_name(np, "lpddr2"); + if (lpddr2_np) { + const struct lpddr2_info *info; + + info = of_lpddr2_get_info(lpddr2_np, dev); + if (info) { + if (info->manufacturer_id >= 0 && + info->manufacturer_id != emc->manufacturer_id) + cfg_mismatches = true; + + if (info->revision_id1 >= 0 && + info->revision_id1 != emc->revision_id1) + cfg_mismatches = true; + + if (info->revision_id2 >= 0 && + info->revision_id2 != emc->revision_id2) + cfg_mismatches = true; + + if (info->density != emc->basic_conf4.density) + cfg_mismatches = true; + + if (info->io_width != emc->basic_conf4.io_width) + cfg_mismatches = true; + + if (info->arch_type != emc->basic_conf4.arch_type) + cfg_mismatches = true; + } else { + dev_err(dev, "failed to parse %pOF\n", lpddr2_np); + cfg_mismatches = true; + } + + of_node_put(lpddr2_np); + } else { + cfg_mismatches = true; + } + + if (cfg_mismatches) { + of_node_put(np); + continue; + } } return np; @@ -455,10 +535,72 @@ tegra_emc_find_node_by_ram_code(struct device *dev) return NULL; } +static int emc_read_lpddr_mode_register(struct tegra_emc *emc, + unsigned int emem_dev, + unsigned int register_addr, + unsigned int *register_data) +{ + u32 memory_dev = emem_dev + 1; + u32 val, mr_mask = 0xff; + int err; + + /* clear data-valid interrupt status */ + writel_relaxed(EMC_MRR_DIVLD_INT, emc->regs + EMC_INTSTATUS); + + /* issue mode register read request */ + val = FIELD_PREP(EMC_MRR_DEV_SELECTN, memory_dev); + val |= FIELD_PREP(EMC_MRR_MRR_MA, register_addr); + + writel_relaxed(val, emc->regs + EMC_MRR); + + /* wait for the LPDDR2 data-valid interrupt */ + err = readl_relaxed_poll_timeout_atomic(emc->regs + EMC_INTSTATUS, val, + val & EMC_MRR_DIVLD_INT, + 1, 100); + if (err) { + dev_err(emc->dev, "mode register %u read failed: %d\n", + register_addr, err); + emc->mrr_error = true; + return err; + } + + /* read out mode register data */ + val = readl_relaxed(emc->regs + EMC_MRR); + *register_data = FIELD_GET(EMC_MRR_MRR_DATA, val) & mr_mask; + + return 0; +} + +static void emc_read_lpddr_sdram_info(struct tegra_emc *emc, + unsigned int emem_dev, + bool print_out) +{ + /* these registers are standard for all LPDDR JEDEC memory chips */ + emc_read_lpddr_mode_register(emc, emem_dev, 5, &emc->manufacturer_id); + emc_read_lpddr_mode_register(emc, emem_dev, 6, &emc->revision_id1); + emc_read_lpddr_mode_register(emc, emem_dev, 7, &emc->revision_id2); + emc_read_lpddr_mode_register(emc, emem_dev, 8, &emc->basic_conf4.value); + + if (!print_out) + return; + + dev_info(emc->dev, "SDRAM[dev%u]: manufacturer: 0x%x (%s) rev1: 0x%x rev2: 0x%x prefetch: S%u density: %uMbit iowidth: %ubit\n", + emem_dev, emc->manufacturer_id, + lpddr2_jedec_manufacturer(emc->manufacturer_id), + emc->revision_id1, emc->revision_id2, + 4 >> emc->basic_conf4.arch_type, + 64 << emc->basic_conf4.density, + 32 >> emc->basic_conf4.io_width); +} + static int emc_setup_hw(struct tegra_emc *emc) { + u32 emc_cfg, emc_dbg, emc_fbio, emc_adr_cfg; u32 intmask = EMC_REFRESH_OVERFLOW_INT; - u32 emc_cfg, emc_dbg, emc_fbio; + static bool print_sdram_info_once; + enum emc_dram_type dram_type; + const char *dram_type_str; + unsigned int emem_numdev; emc_cfg = readl_relaxed(emc->regs + EMC_CFG_2); @@ -496,7 +638,36 @@ static int emc_setup_hw(struct tegra_emc *emc) else emc->dram_bus_width = 32; - dev_info_once(emc->dev, "%ubit DRAM bus\n", emc->dram_bus_width); + dram_type = FIELD_GET(EMC_FBIO_CFG5_DRAM_TYPE, emc_fbio); + + switch (dram_type) { + case DRAM_TYPE_RESERVED: + dram_type_str = "INVALID"; + break; + case DRAM_TYPE_DDR1: + dram_type_str = "DDR1"; + break; + case DRAM_TYPE_LPDDR2: + dram_type_str = "LPDDR2"; + break; + case DRAM_TYPE_DDR2: + dram_type_str = "DDR2"; + break; + } + + emc_adr_cfg = readl_relaxed(emc->regs + EMC_ADR_CFG_0); + emem_numdev = FIELD_GET(EMC_ADR_CFG_0_EMEM_NUMDEV, emc_adr_cfg) + 1; + + dev_info_once(emc->dev, "%ubit DRAM bus, %u %s %s attached\n", + emc->dram_bus_width, emem_numdev, dram_type_str, + emem_numdev == 2 ? "devices" : "device"); + + if (dram_type == DRAM_TYPE_LPDDR2) { + while (emem_numdev--) + emc_read_lpddr_sdram_info(emc, emem_numdev, + !print_sdram_info_once); + print_sdram_info_once = true; + } return 0; } @@ -1049,14 +1220,6 @@ static int tegra_emc_probe(struct platform_device *pdev) emc->clk_nb.notifier_call = tegra_emc_clk_change_notify; emc->dev = &pdev->dev; - np = tegra_emc_find_node_by_ram_code(&pdev->dev); - if (np) { - err = tegra_emc_load_timings_from_dt(emc, np); - of_node_put(np); - if (err) - return err; - } - emc->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(emc->regs)) return PTR_ERR(emc->regs); @@ -1065,6 +1228,14 @@ static int tegra_emc_probe(struct platform_device *pdev) if (err) return err; + np = tegra_emc_find_node_by_ram_code(emc); + if (np) { + err = tegra_emc_load_timings_from_dt(emc, np); + of_node_put(np); + if (err) + return err; + } + err = devm_request_irq(&pdev->dev, irq, tegra_emc_isr, 0, dev_name(&pdev->dev), emc); if (err) { @@ -1117,4 +1288,5 @@ module_platform_driver(tegra_emc_driver); MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>"); MODULE_DESCRIPTION("NVIDIA Tegra20 EMC driver"); +MODULE_SOFTDEP("pre: governor_simpleondemand"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/memory/tegra/tegra210-emc-cc-r21021.c b/drivers/memory/tegra/tegra210-emc-cc-r21021.c index 0ebfa8eccf0c..cc76adb8d7e8 100644 --- a/drivers/memory/tegra/tegra210-emc-cc-r21021.c +++ b/drivers/memory/tegra/tegra210-emc-cc-r21021.c @@ -478,7 +478,7 @@ static u32 periodic_compensation_handler(struct tegra210_emc *emc, u32 type, static u32 tegra210_emc_r21021_periodic_compensation(struct tegra210_emc *emc) { u32 emc_cfg, emc_cfg_o, emc_cfg_update, del, value; - u32 list[] = { + static const u32 list[] = { EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2, diff --git a/drivers/memory/tegra/tegra210-emc-core.c b/drivers/memory/tegra/tegra210-emc-core.c index 06c0f17fa429..13584f9317a4 100644 --- a/drivers/memory/tegra/tegra210-emc-core.c +++ b/drivers/memory/tegra/tegra210-emc-core.c @@ -1662,7 +1662,7 @@ static int tegra210_emc_debug_min_rate_set(void *data, u64 rate) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(tegra210_emc_debug_min_rate_fops, +DEFINE_DEBUGFS_ATTRIBUTE(tegra210_emc_debug_min_rate_fops, tegra210_emc_debug_min_rate_get, tegra210_emc_debug_min_rate_set, "%llu\n"); @@ -1692,7 +1692,7 @@ static int tegra210_emc_debug_max_rate_set(void *data, u64 rate) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(tegra210_emc_debug_max_rate_fops, +DEFINE_DEBUGFS_ATTRIBUTE(tegra210_emc_debug_max_rate_fops, tegra210_emc_debug_max_rate_get, tegra210_emc_debug_max_rate_set, "%llu\n"); @@ -1723,7 +1723,7 @@ static int tegra210_emc_debug_temperature_set(void *data, u64 temperature) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(tegra210_emc_debug_temperature_fops, +DEFINE_DEBUGFS_ATTRIBUTE(tegra210_emc_debug_temperature_fops, tegra210_emc_debug_temperature_get, tegra210_emc_debug_temperature_set, "%llu\n"); diff --git a/drivers/memory/tegra/tegra30-emc.c b/drivers/memory/tegra/tegra30-emc.c index 7e21a852f2e1..80f98d717e13 100644 --- a/drivers/memory/tegra/tegra30-emc.c +++ b/drivers/memory/tegra/tegra30-emc.c @@ -1289,7 +1289,7 @@ static int tegra_emc_debug_min_rate_set(void *data, u64 rate) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(tegra_emc_debug_min_rate_fops, +DEFINE_DEBUGFS_ATTRIBUTE(tegra_emc_debug_min_rate_fops, tegra_emc_debug_min_rate_get, tegra_emc_debug_min_rate_set, "%llu\n"); @@ -1319,7 +1319,7 @@ static int tegra_emc_debug_max_rate_set(void *data, u64 rate) return 0; } -DEFINE_SIMPLE_ATTRIBUTE(tegra_emc_debug_max_rate_fops, +DEFINE_DEBUGFS_ATTRIBUTE(tegra_emc_debug_max_rate_fops, tegra_emc_debug_max_rate_get, tegra_emc_debug_max_rate_set, "%llu\n"); diff --git a/drivers/of/platform.c b/drivers/of/platform.c index 32d5ff8df747..07813fb1ef37 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -505,6 +505,7 @@ EXPORT_SYMBOL_GPL(of_platform_default_populate); static const struct of_device_id reserved_mem_matches[] = { { .compatible = "qcom,rmtfs-mem" }, { .compatible = "qcom,cmd-db" }, + { .compatible = "qcom,smem" }, { .compatible = "ramoops" }, { .compatible = "nvmem-rmem" }, {} diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index b0056ae5d463..85024eb1d2ea 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -58,7 +58,7 @@ config RESET_BRCMSTB a SUN_TOP_CTRL_SW_INIT style controller. config RESET_BRCMSTB_RESCAL - bool "Broadcom STB RESCAL reset controller" + tristate "Broadcom STB RESCAL reset controller" depends on HAS_IOMEM depends on ARCH_BRCMSTB || COMPILE_TEST default ARCH_BRCMSTB @@ -116,7 +116,7 @@ config RESET_LPC18XX config RESET_MCHP_SPARX5 bool "Microchip Sparx5 reset driver" - depends on ARCH_SPARX5 || COMPILE_TEST + depends on ARCH_SPARX5 || SOC_LAN966 || COMPILE_TEST default y if SPARX5_SWITCH select MFD_SYSCON help diff --git a/drivers/reset/reset-microchip-sparx5.c b/drivers/reset/reset-microchip-sparx5.c index f01e7db8e83b..00b612a0effa 100644 --- a/drivers/reset/reset-microchip-sparx5.c +++ b/drivers/reset/reset-microchip-sparx5.c @@ -13,15 +13,18 @@ #include <linux/regmap.h> #include <linux/reset-controller.h> -#define PROTECT_REG 0x84 -#define PROTECT_BIT BIT(10) -#define SOFT_RESET_REG 0x00 -#define SOFT_RESET_BIT BIT(1) +struct reset_props { + u32 protect_reg; + u32 protect_bit; + u32 reset_reg; + u32 reset_bit; +}; struct mchp_reset_context { struct regmap *cpu_ctrl; struct regmap *gcb_ctrl; struct reset_controller_dev rcdev; + const struct reset_props *props; }; static struct regmap_config sparx5_reset_regmap_config = { @@ -38,14 +41,16 @@ static int sparx5_switch_reset(struct reset_controller_dev *rcdev, u32 val; /* Make sure the core is PROTECTED from reset */ - regmap_update_bits(ctx->cpu_ctrl, PROTECT_REG, PROTECT_BIT, PROTECT_BIT); + regmap_update_bits(ctx->cpu_ctrl, ctx->props->protect_reg, + ctx->props->protect_bit, ctx->props->protect_bit); /* Start soft reset */ - regmap_write(ctx->gcb_ctrl, SOFT_RESET_REG, SOFT_RESET_BIT); + regmap_write(ctx->gcb_ctrl, ctx->props->reset_reg, + ctx->props->reset_bit); /* Wait for soft reset done */ - return regmap_read_poll_timeout(ctx->gcb_ctrl, SOFT_RESET_REG, val, - (val & SOFT_RESET_BIT) == 0, + return regmap_read_poll_timeout(ctx->gcb_ctrl, ctx->props->reset_reg, val, + (val & ctx->props->reset_bit) == 0, 1, 100); } @@ -115,13 +120,32 @@ static int mchp_sparx5_reset_probe(struct platform_device *pdev) ctx->rcdev.nr_resets = 1; ctx->rcdev.ops = &sparx5_reset_ops; ctx->rcdev.of_node = dn; + ctx->props = device_get_match_data(&pdev->dev); return devm_reset_controller_register(&pdev->dev, &ctx->rcdev); } +static const struct reset_props reset_props_sparx5 = { + .protect_reg = 0x84, + .protect_bit = BIT(10), + .reset_reg = 0x0, + .reset_bit = BIT(1), +}; + +static const struct reset_props reset_props_lan966x = { + .protect_reg = 0x88, + .protect_bit = BIT(5), + .reset_reg = 0x0, + .reset_bit = BIT(1), +}; + static const struct of_device_id mchp_sparx5_reset_of_match[] = { { .compatible = "microchip,sparx5-switch-reset", + .data = &reset_props_sparx5, + }, { + .compatible = "microchip,lan966x-switch-reset", + .data = &reset_props_lan966x, }, { } }; diff --git a/drivers/reset/reset-uniphier-glue.c b/drivers/reset/reset-uniphier-glue.c index 027990b79f61..908c1d5bc41e 100644 --- a/drivers/reset/reset-uniphier-glue.c +++ b/drivers/reset/reset-uniphier-glue.c @@ -156,6 +156,10 @@ static const struct of_device_id uniphier_glue_reset_match[] = { .data = &uniphier_pxs2_data, }, { + .compatible = "socionext,uniphier-nx1-usb3-reset", + .data = &uniphier_pxs2_data, + }, + { .compatible = "socionext,uniphier-pro4-ahci-reset", .data = &uniphier_pro4_data, }, diff --git a/drivers/reset/reset-uniphier.c b/drivers/reset/reset-uniphier.c index 5f75783f9397..ff7580f38056 100644 --- a/drivers/reset/reset-uniphier.c +++ b/drivers/reset/reset-uniphier.c @@ -136,6 +136,21 @@ static const struct uniphier_reset_data uniphier_pxs3_sys_reset_data[] = { UNIPHIER_RESETX(28, 0x200c, 7), /* SATA0 */ UNIPHIER_RESETX(29, 0x200c, 8), /* SATA1 */ UNIPHIER_RESETX(30, 0x200c, 21), /* SATA-PHY */ + UNIPHIER_RESETX(40, 0x2008, 0), /* AIO */ + UNIPHIER_RESETX(42, 0x2010, 2), /* EXIV */ + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_nx1_sys_reset_data[] = { + UNIPHIER_RESETX(4, 0x2008, 8), /* eMMC */ + UNIPHIER_RESETX(6, 0x200c, 0), /* Ether */ + UNIPHIER_RESETX(12, 0x200c, 16), /* USB30 link */ + UNIPHIER_RESETX(16, 0x200c, 24), /* USB30-PHY0 */ + UNIPHIER_RESETX(17, 0x200c, 25), /* USB30-PHY1 */ + UNIPHIER_RESETX(18, 0x200c, 26), /* USB30-PHY2 */ + UNIPHIER_RESETX(24, 0x200c, 8), /* PCIe */ + UNIPHIER_RESETX(52, 0x2010, 0), /* VOC */ + UNIPHIER_RESETX(58, 0x2010, 8), /* HDMI-Tx */ UNIPHIER_RESET_END, }; @@ -400,6 +415,10 @@ static const struct of_device_id uniphier_reset_match[] = { .compatible = "socionext,uniphier-pxs3-reset", .data = uniphier_pxs3_sys_reset_data, }, + { + .compatible = "socionext,uniphier-nx1-reset", + .data = uniphier_nx1_sys_reset_data, + }, /* Media I/O reset, SD reset */ { .compatible = "socionext,uniphier-ld4-mio-reset", @@ -437,6 +456,10 @@ static const struct of_device_id uniphier_reset_match[] = { .compatible = "socionext,uniphier-pxs3-sd-reset", .data = uniphier_pro5_sd_reset_data, }, + { + .compatible = "socionext,uniphier-nx1-sd-reset", + .data = uniphier_pro5_sd_reset_data, + }, /* Peripheral reset */ { .compatible = "socionext,uniphier-ld4-peri-reset", @@ -470,6 +493,10 @@ static const struct of_device_id uniphier_reset_match[] = { .compatible = "socionext,uniphier-pxs3-peri-reset", .data = uniphier_pro4_peri_reset_data, }, + { + .compatible = "socionext,uniphier-nx1-peri-reset", + .data = uniphier_pro4_peri_reset_data, + }, /* Analog signal amplifiers reset */ { .compatible = "socionext,uniphier-ld11-adamv-reset", diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index e1bc5214494e..7208eeb8459a 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1404,16 +1404,10 @@ config RTC_DRV_OMAP This driver can also be built as a module, if so, module will be called rtc-omap. -config HAVE_S3C_RTC - bool - help - This will include RTC support for Samsung SoCs. If - you want to include RTC support for any machine, kindly - select this in the respective mach-XXXX/Kconfig file. - config RTC_DRV_S3C tristate "Samsung S3C series SoC RTC" - depends on ARCH_S3C64XX || HAVE_S3C_RTC || COMPILE_TEST + depends on ARCH_EXYNOS || ARCH_S3C64XX || ARCH_S3C24XX || ARCH_S5PV210 || \ + COMPILE_TEST help RTC (Realtime Clock) driver for the clock inbuilt into the Samsung S3C24XX series of SoCs. This can provide periodic diff --git a/drivers/soc/amlogic/meson-canvas.c b/drivers/soc/amlogic/meson-canvas.c index d0329ad170d1..383b0cfc584e 100644 --- a/drivers/soc/amlogic/meson-canvas.c +++ b/drivers/soc/amlogic/meson-canvas.c @@ -168,7 +168,6 @@ EXPORT_SYMBOL_GPL(meson_canvas_free); static int meson_canvas_probe(struct platform_device *pdev) { - struct resource *res; struct meson_canvas *canvas; struct device *dev = &pdev->dev; @@ -176,8 +175,7 @@ static int meson_canvas_probe(struct platform_device *pdev) if (!canvas) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - canvas->reg_base = devm_ioremap_resource(dev, res); + canvas->reg_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(canvas->reg_base)) return PTR_ERR(canvas->reg_base); diff --git a/drivers/soc/amlogic/meson-clk-measure.c b/drivers/soc/amlogic/meson-clk-measure.c index 6dd190270123..3f3039600357 100644 --- a/drivers/soc/amlogic/meson-clk-measure.c +++ b/drivers/soc/amlogic/meson-clk-measure.c @@ -606,7 +606,6 @@ static int meson_msr_probe(struct platform_device *pdev) { const struct meson_msr_id *match_data; struct meson_msr *priv; - struct resource *res; struct dentry *root, *clks; void __iomem *base; int i; @@ -624,8 +623,7 @@ static int meson_msr_probe(struct platform_device *pdev) memcpy(priv->msr_table, match_data, sizeof(priv->msr_table)); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); diff --git a/drivers/soc/amlogic/meson-gx-socinfo.c b/drivers/soc/amlogic/meson-gx-socinfo.c index 6f54bd832c8b..165f7548401b 100644 --- a/drivers/soc/amlogic/meson-gx-socinfo.c +++ b/drivers/soc/amlogic/meson-gx-socinfo.c @@ -65,6 +65,7 @@ static const struct meson_gx_package_id { { "A113X", 0x25, 0x37, 0xff }, { "A113D", 0x25, 0x22, 0xff }, { "S905D2", 0x28, 0x10, 0xf0 }, + { "S905Y2", 0x28, 0x30, 0xf0 }, { "S905X2", 0x28, 0x40, 0xf0 }, { "A311D", 0x29, 0x10, 0xf0 }, { "S922X", 0x29, 0x40, 0xf0 }, diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig index 243ca196e6ad..f579ee0b5afa 100644 --- a/drivers/soc/aspeed/Kconfig +++ b/drivers/soc/aspeed/Kconfig @@ -24,6 +24,16 @@ config ASPEED_LPC_SNOOP allows the BMC to listen on and save the data written by the host to an arbitrary LPC I/O port. +config ASPEED_UART_ROUTING + tristate "ASPEED uart routing control" + select REGMAP + select MFD_SYSCON + default ARCH_ASPEED + help + Provides a driver to control the UART routing paths, allowing + users to perform runtime configuration of the RX muxes among + the UART controllers and I/O pins. + config ASPEED_P2A_CTRL tristate "ASPEED P2A (VGA MMIO to BMC) bridge control" select REGMAP diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile index fcab7192e1a4..b35d74592964 100644 --- a/drivers/soc/aspeed/Makefile +++ b/drivers/soc/aspeed/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o -obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o -obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o -obj-$(CONFIG_ASPEED_SOCINFO) += aspeed-socinfo.o +obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o +obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o +obj-$(CONFIG_ASPEED_UART_ROUTING) += aspeed-uart-routing.o +obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o +obj-$(CONFIG_ASPEED_SOCINFO) += aspeed-socinfo.o diff --git a/drivers/soc/aspeed/aspeed-uart-routing.c b/drivers/soc/aspeed/aspeed-uart-routing.c new file mode 100644 index 000000000000..ef8b24fd1851 --- /dev/null +++ b/drivers/soc/aspeed/aspeed-uart-routing.c @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018 Google LLC + * Copyright (c) 2021 Aspeed Technology Inc. + */ +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> + +/* register offsets */ +#define HICR9 0x98 +#define HICRA 0x9c + +/* attributes options */ +#define UART_ROUTING_IO1 "io1" +#define UART_ROUTING_IO2 "io2" +#define UART_ROUTING_IO3 "io3" +#define UART_ROUTING_IO4 "io4" +#define UART_ROUTING_IO5 "io5" +#define UART_ROUTING_IO6 "io6" +#define UART_ROUTING_IO10 "io10" +#define UART_ROUTING_UART1 "uart1" +#define UART_ROUTING_UART2 "uart2" +#define UART_ROUTING_UART3 "uart3" +#define UART_ROUTING_UART4 "uart4" +#define UART_ROUTING_UART5 "uart5" +#define UART_ROUTING_UART6 "uart6" +#define UART_ROUTING_UART10 "uart10" +#define UART_ROUTING_RES "reserved" + +struct aspeed_uart_routing { + struct regmap *map; + struct attribute_group const *attr_grp; +}; + +struct aspeed_uart_routing_selector { + struct device_attribute dev_attr; + uint8_t reg; + uint8_t mask; + uint8_t shift; + const char *const options[]; +}; + +#define to_routing_selector(_dev_attr) \ + container_of(_dev_attr, struct aspeed_uart_routing_selector, dev_attr) + +static ssize_t aspeed_uart_routing_show(struct device *dev, + struct device_attribute *attr, + char *buf); + +static ssize_t aspeed_uart_routing_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); + +#define ROUTING_ATTR(_name) { \ + .attr = {.name = _name, \ + .mode = VERIFY_OCTAL_PERMISSIONS(0644) }, \ + .show = aspeed_uart_routing_show, \ + .store = aspeed_uart_routing_store, \ +} + +/* routing selector for AST25xx */ +static struct aspeed_uart_routing_selector ast2500_io6_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO6), + .reg = HICR9, + .shift = 8, + .mask = 0xf, + .options = { + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART5, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO5, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_uart5_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART5), + .reg = HICRA, + .shift = 28, + .mask = 0xf, + .options = { + UART_ROUTING_IO5, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_uart4_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART4), + .reg = HICRA, + .shift = 25, + .mask = 0x7, + .options = { + UART_ROUTING_IO4, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_uart3_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART3), + .reg = HICRA, + .shift = 22, + .mask = 0x7, + .options = { + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_UART4, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_uart2_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART2), + .reg = HICRA, + .shift = 19, + .mask = 0x7, + .options = { + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO1, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART1, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_uart1_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART1), + .reg = HICRA, + .shift = 16, + .mask = 0x7, + .options = { + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_io5_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO5), + .reg = HICRA, + .shift = 12, + .mask = 0x7, + .options = { + UART_ROUTING_UART5, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_IO1, + UART_ROUTING_IO3, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_io4_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO4), + .reg = HICRA, + .shift = 9, + .mask = 0x7, + .options = { + UART_ROUTING_UART4, + UART_ROUTING_UART5, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_io3_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO3), + .reg = HICRA, + .shift = 6, + .mask = 0x7, + .options = { + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART5, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_io2_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO2), + .reg = HICRA, + .shift = 3, + .mask = 0x7, + .options = { + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART5, + UART_ROUTING_UART1, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2500_io1_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO1), + .reg = HICRA, + .shift = 0, + .mask = 0x7, + .options = { + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART5, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO6, + NULL, + }, +}; + +static struct attribute *ast2500_uart_routing_attrs[] = { + &ast2500_io6_sel.dev_attr.attr, + &ast2500_uart5_sel.dev_attr.attr, + &ast2500_uart4_sel.dev_attr.attr, + &ast2500_uart3_sel.dev_attr.attr, + &ast2500_uart2_sel.dev_attr.attr, + &ast2500_uart1_sel.dev_attr.attr, + &ast2500_io5_sel.dev_attr.attr, + &ast2500_io4_sel.dev_attr.attr, + &ast2500_io3_sel.dev_attr.attr, + &ast2500_io2_sel.dev_attr.attr, + &ast2500_io1_sel.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ast2500_uart_routing_attr_group = { + .attrs = ast2500_uart_routing_attrs, +}; + +/* routing selector for AST26xx */ +static struct aspeed_uart_routing_selector ast2600_uart10_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART10), + .reg = HICR9, + .shift = 12, + .mask = 0xf, + .options = { + UART_ROUTING_IO10, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_RES, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_io10_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO10), + .reg = HICR9, + .shift = 8, + .mask = 0xf, + .options = { + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_RES, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_RES, + UART_ROUTING_UART10, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_uart4_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART4), + .reg = HICRA, + .shift = 25, + .mask = 0x7, + .options = { + UART_ROUTING_IO4, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_IO10, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_uart3_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART3), + .reg = HICRA, + .shift = 22, + .mask = 0x7, + .options = { + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_UART4, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_IO10, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_uart2_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART2), + .reg = HICRA, + .shift = 19, + .mask = 0x7, + .options = { + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO1, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART1, + UART_ROUTING_IO10, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_uart1_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_UART1), + .reg = HICRA, + .shift = 16, + .mask = 0x7, + .options = { + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_IO10, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_io4_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO4), + .reg = HICRA, + .shift = 9, + .mask = 0x7, + .options = { + UART_ROUTING_UART4, + UART_ROUTING_UART10, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO10, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_io3_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO3), + .reg = HICRA, + .shift = 6, + .mask = 0x7, + .options = { + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART10, + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_IO1, + UART_ROUTING_IO2, + UART_ROUTING_IO10, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_io2_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO2), + .reg = HICRA, + .shift = 3, + .mask = 0x7, + .options = { + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART10, + UART_ROUTING_UART1, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO10, + NULL, + }, +}; + +static struct aspeed_uart_routing_selector ast2600_io1_sel = { + .dev_attr = ROUTING_ATTR(UART_ROUTING_IO1), + .reg = HICRA, + .shift = 0, + .mask = 0x7, + .options = { + UART_ROUTING_UART1, + UART_ROUTING_UART2, + UART_ROUTING_UART3, + UART_ROUTING_UART4, + UART_ROUTING_UART10, + UART_ROUTING_IO3, + UART_ROUTING_IO4, + UART_ROUTING_IO10, + NULL, + }, +}; + +static struct attribute *ast2600_uart_routing_attrs[] = { + &ast2600_uart10_sel.dev_attr.attr, + &ast2600_io10_sel.dev_attr.attr, + &ast2600_uart4_sel.dev_attr.attr, + &ast2600_uart3_sel.dev_attr.attr, + &ast2600_uart2_sel.dev_attr.attr, + &ast2600_uart1_sel.dev_attr.attr, + &ast2600_io4_sel.dev_attr.attr, + &ast2600_io3_sel.dev_attr.attr, + &ast2600_io2_sel.dev_attr.attr, + &ast2600_io1_sel.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ast2600_uart_routing_attr_group = { + .attrs = ast2600_uart_routing_attrs, +}; + +static ssize_t aspeed_uart_routing_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct aspeed_uart_routing *uart_routing = dev_get_drvdata(dev); + struct aspeed_uart_routing_selector *sel = to_routing_selector(attr); + int val, pos, len; + + regmap_read(uart_routing->map, sel->reg, &val); + val = (val >> sel->shift) & sel->mask; + + len = 0; + for (pos = 0; sel->options[pos] != NULL; ++pos) { + if (pos == val) + len += sysfs_emit_at(buf, len, "[%s] ", sel->options[pos]); + else + len += sysfs_emit_at(buf, len, "%s ", sel->options[pos]); + } + + if (val >= pos) + len += sysfs_emit_at(buf, len, "[unknown(%d)]", val); + + len += sysfs_emit_at(buf, len, "\n"); + + return len; +} + +static ssize_t aspeed_uart_routing_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct aspeed_uart_routing *uart_routing = dev_get_drvdata(dev); + struct aspeed_uart_routing_selector *sel = to_routing_selector(attr); + int val; + + val = match_string(sel->options, -1, buf); + if (val < 0) { + dev_err(dev, "invalid value \"%s\"\n", buf); + return -EINVAL; + } + + regmap_update_bits(uart_routing->map, sel->reg, + (sel->mask << sel->shift), + (val & sel->mask) << sel->shift); + + return count; +} + +static int aspeed_uart_routing_probe(struct platform_device *pdev) +{ + int rc; + struct device *dev = &pdev->dev; + struct aspeed_uart_routing *uart_routing; + + uart_routing = devm_kzalloc(&pdev->dev, sizeof(*uart_routing), GFP_KERNEL); + if (!uart_routing) + return -ENOMEM; + + uart_routing->map = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(uart_routing->map)) { + dev_err(dev, "cannot get regmap\n"); + return PTR_ERR(uart_routing->map); + } + + uart_routing->attr_grp = of_device_get_match_data(dev); + + rc = sysfs_create_group(&dev->kobj, uart_routing->attr_grp); + if (rc < 0) + return rc; + + dev_set_drvdata(dev, uart_routing); + + dev_info(dev, "module loaded\n"); + + return 0; +} + +static int aspeed_uart_routing_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct aspeed_uart_routing *uart_routing = platform_get_drvdata(pdev); + + sysfs_remove_group(&dev->kobj, uart_routing->attr_grp); + + return 0; +} + +static const struct of_device_id aspeed_uart_routing_table[] = { + { .compatible = "aspeed,ast2400-uart-routing", + .data = &ast2500_uart_routing_attr_group }, + { .compatible = "aspeed,ast2500-uart-routing", + .data = &ast2500_uart_routing_attr_group }, + { .compatible = "aspeed,ast2600-uart-routing", + .data = &ast2600_uart_routing_attr_group }, + { }, +}; + +static struct platform_driver aspeed_uart_routing_driver = { + .driver = { + .name = "aspeed-uart-routing", + .of_match_table = aspeed_uart_routing_table, + }, + .probe = aspeed_uart_routing_probe, + .remove = aspeed_uart_routing_remove, +}; + +module_platform_driver(aspeed_uart_routing_driver); + +MODULE_AUTHOR("Oskar Senft <osk@google.com>"); +MODULE_AUTHOR("Chia-Wei Wang <chiawei_wang@aspeedtech.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Driver to configure Aspeed UART routing"); diff --git a/drivers/soc/bcm/bcm63xx/bcm-pmb.c b/drivers/soc/bcm/bcm63xx/bcm-pmb.c index 774465c119be..7bbe46ea5f94 100644 --- a/drivers/soc/bcm/bcm63xx/bcm-pmb.c +++ b/drivers/soc/bcm/bcm63xx/bcm-pmb.c @@ -276,7 +276,6 @@ static int bcm_pmb_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; const struct bcm_pmb_pd_data *table; const struct bcm_pmb_pd_data *e; - struct resource *res; struct bcm_pmb *pmb; int max_id; int err; @@ -287,8 +286,7 @@ static int bcm_pmb_probe(struct platform_device *pdev) pmb->dev = dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - pmb->base = devm_ioremap_resource(&pdev->dev, res); + pmb->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(pmb->base)) return PTR_ERR(pmb->base); diff --git a/drivers/soc/bcm/bcm63xx/bcm63xx-power.c b/drivers/soc/bcm/bcm63xx/bcm63xx-power.c index 515fe182dc34..aa72e13d5d0e 100644 --- a/drivers/soc/bcm/bcm63xx/bcm63xx-power.c +++ b/drivers/soc/bcm/bcm63xx/bcm63xx-power.c @@ -91,7 +91,6 @@ static int bcm63xx_power_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; - struct resource *res; const struct bcm63xx_power_data *entry, *table; struct bcm63xx_power *power; unsigned int ndom; @@ -102,8 +101,7 @@ static int bcm63xx_power_probe(struct platform_device *pdev) if (!power) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - power->base = devm_ioremap_resource(&pdev->dev, res); + power->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(power->base)) return PTR_ERR(power->base); diff --git a/drivers/soc/bcm/brcmstb/biuctrl.c b/drivers/soc/bcm/brcmstb/biuctrl.c index 7f8dc302ae6e..2c975d79fe8e 100644 --- a/drivers/soc/bcm/brcmstb/biuctrl.c +++ b/drivers/soc/bcm/brcmstb/biuctrl.c @@ -136,6 +136,8 @@ static int __init mcp_write_pairing_set(void) static const u32 a72_b53_mach_compat[] = { 0x7211, + 0x72113, + 0x72116, 0x7216, 0x72164, 0x72165, diff --git a/drivers/soc/fsl/dpio/dpio-service.c b/drivers/soc/fsl/dpio/dpio-service.c index 3fd0d0840287..db0d3910fee4 100644 --- a/drivers/soc/fsl/dpio/dpio-service.c +++ b/drivers/soc/fsl/dpio/dpio-service.c @@ -500,7 +500,7 @@ int dpaa2_io_service_enqueue_multiple_fq(struct dpaa2_io *d, qbman_eq_desc_set_no_orp(&ed, 0); qbman_eq_desc_set_fq(&ed, fqid); - return qbman_swp_enqueue_multiple(d->swp, &ed, fd, 0, nb); + return qbman_swp_enqueue_multiple(d->swp, &ed, fd, NULL, nb); } EXPORT_SYMBOL(dpaa2_io_service_enqueue_multiple_fq); diff --git a/drivers/soc/fsl/dpio/qbman-portal.c b/drivers/soc/fsl/dpio/qbman-portal.c index 3474bf5f88d5..e46bcf78ed59 100644 --- a/drivers/soc/fsl/dpio/qbman-portal.c +++ b/drivers/soc/fsl/dpio/qbman-portal.c @@ -693,9 +693,9 @@ int qbman_swp_enqueue_multiple_direct(struct qbman_swp *s, p = (s->addr_cena + QBMAN_CENA_SWP_EQCR(eqcr_pi & half_mask)); p[0] = cl[0] | s->eqcr.pi_vb; if (flags && (flags[i] & QBMAN_ENQUEUE_FLAG_DCA)) { - struct qbman_eq_desc *d = (struct qbman_eq_desc *)p; + struct qbman_eq_desc *eq_desc = (struct qbman_eq_desc *)p; - d->dca = (1 << QB_ENQUEUE_CMD_DCA_EN_SHIFT) | + eq_desc->dca = (1 << QB_ENQUEUE_CMD_DCA_EN_SHIFT) | ((flags[i]) & QBMAN_EQCR_DCA_IDXMASK); } eqcr_pi++; @@ -775,9 +775,9 @@ int qbman_swp_enqueue_multiple_mem_back(struct qbman_swp *s, p = (s->addr_cena + QBMAN_CENA_SWP_EQCR(eqcr_pi & half_mask)); p[0] = cl[0] | s->eqcr.pi_vb; if (flags && (flags[i] & QBMAN_ENQUEUE_FLAG_DCA)) { - struct qbman_eq_desc *d = (struct qbman_eq_desc *)p; + struct qbman_eq_desc *eq_desc = (struct qbman_eq_desc *)p; - d->dca = (1 << QB_ENQUEUE_CMD_DCA_EN_SHIFT) | + eq_desc->dca = (1 << QB_ENQUEUE_CMD_DCA_EN_SHIFT) | ((flags[i]) & QBMAN_EQCR_DCA_IDXMASK); } eqcr_pi++; diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index d5e9a5f2c087..072473a16f4d 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -140,7 +140,6 @@ static int fsl_guts_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; - struct resource *res; const struct fsl_soc_die_attr *soc_die; const char *machine; u32 svr; @@ -152,8 +151,7 @@ static int fsl_guts_probe(struct platform_device *pdev) guts->little_endian = of_property_read_bool(np, "little-endian"); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - guts->regs = devm_ioremap_resource(dev, res); + guts->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(guts->regs)) return PTR_ERR(guts->regs); diff --git a/drivers/soc/fsl/rcpm.c b/drivers/soc/fsl/rcpm.c index 90d3f4060b0c..3d0cae30c769 100644 --- a/drivers/soc/fsl/rcpm.c +++ b/drivers/soc/fsl/rcpm.c @@ -146,7 +146,6 @@ static const struct dev_pm_ops rcpm_pm_ops = { static int rcpm_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *r; struct rcpm *rcpm; int ret; @@ -154,11 +153,7 @@ static int rcpm_probe(struct platform_device *pdev) if (!rcpm) return -ENOMEM; - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!r) - return -ENODEV; - - rcpm->ippdexpcr_base = devm_ioremap_resource(&pdev->dev, r); + rcpm->ippdexpcr_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(rcpm->ippdexpcr_base)) { ret = PTR_ERR(rcpm->ippdexpcr_base); return ret; diff --git a/drivers/soc/imx/Kconfig b/drivers/soc/imx/Kconfig index 05812f8ae734..a840494e849a 100644 --- a/drivers/soc/imx/Kconfig +++ b/drivers/soc/imx/Kconfig @@ -6,6 +6,7 @@ config IMX_GPCV2_PM_DOMAINS depends on ARCH_MXC || (COMPILE_TEST && OF) depends on PM select PM_GENERIC_DOMAINS + select REGMAP_MMIO default y if SOC_IMX7D config SOC_IMX8M diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile index 078dc918f4f3..8a707077914c 100644 --- a/drivers/soc/imx/Makefile +++ b/drivers/soc/imx/Makefile @@ -5,3 +5,4 @@ endif obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o obj-$(CONFIG_IMX_GPCV2_PM_DOMAINS) += gpcv2.o obj-$(CONFIG_SOC_IMX8M) += soc-imx8m.o +obj-$(CONFIG_SOC_IMX8M) += imx8m-blk-ctrl.o diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c index 34a9ac1f2b9b..b8d52d8d29db 100644 --- a/drivers/soc/imx/gpcv2.c +++ b/drivers/soc/imx/gpcv2.c @@ -192,7 +192,7 @@ struct imx_pgc_domain { struct clk_bulk_data *clks; int num_clks; - unsigned int pgc; + unsigned long pgc; const struct { u32 pxx; @@ -202,6 +202,7 @@ struct imx_pgc_domain { } bits; const int voltage; + const bool keep_clocks; struct device *dev; }; @@ -220,7 +221,7 @@ to_imx_pgc_domain(struct generic_pm_domain *genpd) static int imx_pgc_power_up(struct generic_pm_domain *genpd) { struct imx_pgc_domain *domain = to_imx_pgc_domain(genpd); - u32 reg_val; + u32 reg_val, pgc; int ret; ret = pm_runtime_get_sync(domain->dev); @@ -244,6 +245,8 @@ static int imx_pgc_power_up(struct generic_pm_domain *genpd) goto out_regulator_disable; } + reset_control_assert(domain->reset); + if (domain->bits.pxx) { /* request the domain to power up */ regmap_update_bits(domain->regmap, GPC_PU_PGC_SW_PUP_REQ, @@ -262,12 +265,12 @@ static int imx_pgc_power_up(struct generic_pm_domain *genpd) } /* disable power control */ - regmap_clear_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc), - GPC_PGC_CTRL_PCR); + for_each_set_bit(pgc, &domain->pgc, 32) { + regmap_clear_bits(domain->regmap, GPC_PGC_CTRL(pgc), + GPC_PGC_CTRL_PCR); + } } - reset_control_assert(domain->reset); - /* delay for reset to propagate */ udelay(5); @@ -293,7 +296,8 @@ static int imx_pgc_power_up(struct generic_pm_domain *genpd) } /* Disable reset clocks for all devices in the domain */ - clk_bulk_disable_unprepare(domain->num_clks, domain->clks); + if (!domain->keep_clocks) + clk_bulk_disable_unprepare(domain->num_clks, domain->clks); return 0; @@ -311,14 +315,16 @@ out_put_pm: static int imx_pgc_power_down(struct generic_pm_domain *genpd) { struct imx_pgc_domain *domain = to_imx_pgc_domain(genpd); - u32 reg_val; + u32 reg_val, pgc; int ret; /* Enable reset clocks for all devices in the domain */ - ret = clk_bulk_prepare_enable(domain->num_clks, domain->clks); - if (ret) { - dev_err(domain->dev, "failed to enable reset clocks\n"); - return ret; + if (!domain->keep_clocks) { + ret = clk_bulk_prepare_enable(domain->num_clks, domain->clks); + if (ret) { + dev_err(domain->dev, "failed to enable reset clocks\n"); + return ret; + } } /* request the ADB400 to power down */ @@ -338,8 +344,10 @@ static int imx_pgc_power_down(struct generic_pm_domain *genpd) if (domain->bits.pxx) { /* enable power control */ - regmap_update_bits(domain->regmap, GPC_PGC_CTRL(domain->pgc), - GPC_PGC_CTRL_PCR, GPC_PGC_CTRL_PCR); + for_each_set_bit(pgc, &domain->pgc, 32) { + regmap_update_bits(domain->regmap, GPC_PGC_CTRL(pgc), + GPC_PGC_CTRL_PCR, GPC_PGC_CTRL_PCR); + } /* request the domain to power down */ regmap_update_bits(domain->regmap, GPC_PU_PGC_SW_PDN_REQ, @@ -389,7 +397,7 @@ static const struct imx_pgc_domain imx7_pgc_domains[] = { .map = IMX7_MIPI_PHY_A_CORE_DOMAIN, }, .voltage = 1000000, - .pgc = IMX7_PGC_MIPI, + .pgc = BIT(IMX7_PGC_MIPI), }, [IMX7_POWER_DOMAIN_PCIE_PHY] = { @@ -401,7 +409,7 @@ static const struct imx_pgc_domain imx7_pgc_domains[] = { .map = IMX7_PCIE_PHY_A_CORE_DOMAIN, }, .voltage = 1000000, - .pgc = IMX7_PGC_PCIE, + .pgc = BIT(IMX7_PGC_PCIE), }, [IMX7_POWER_DOMAIN_USB_HSIC_PHY] = { @@ -413,7 +421,7 @@ static const struct imx_pgc_domain imx7_pgc_domains[] = { .map = IMX7_USB_HSIC_PHY_A_CORE_DOMAIN, }, .voltage = 1200000, - .pgc = IMX7_PGC_USB_HSIC, + .pgc = BIT(IMX7_PGC_USB_HSIC), }, }; @@ -448,7 +456,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .pxx = IMX8M_MIPI_SW_Pxx_REQ, .map = IMX8M_MIPI_A53_DOMAIN, }, - .pgc = IMX8M_PGC_MIPI, + .pgc = BIT(IMX8M_PGC_MIPI), }, [IMX8M_POWER_DOMAIN_PCIE1] = { @@ -459,7 +467,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .pxx = IMX8M_PCIE1_SW_Pxx_REQ, .map = IMX8M_PCIE1_A53_DOMAIN, }, - .pgc = IMX8M_PGC_PCIE1, + .pgc = BIT(IMX8M_PGC_PCIE1), }, [IMX8M_POWER_DOMAIN_USB_OTG1] = { @@ -470,7 +478,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .pxx = IMX8M_OTG1_SW_Pxx_REQ, .map = IMX8M_OTG1_A53_DOMAIN, }, - .pgc = IMX8M_PGC_OTG1, + .pgc = BIT(IMX8M_PGC_OTG1), }, [IMX8M_POWER_DOMAIN_USB_OTG2] = { @@ -481,7 +489,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .pxx = IMX8M_OTG2_SW_Pxx_REQ, .map = IMX8M_OTG2_A53_DOMAIN, }, - .pgc = IMX8M_PGC_OTG2, + .pgc = BIT(IMX8M_PGC_OTG2), }, [IMX8M_POWER_DOMAIN_DDR1] = { @@ -492,7 +500,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .pxx = IMX8M_DDR1_SW_Pxx_REQ, .map = IMX8M_DDR2_A53_DOMAIN, }, - .pgc = IMX8M_PGC_DDR1, + .pgc = BIT(IMX8M_PGC_DDR1), }, [IMX8M_POWER_DOMAIN_GPU] = { @@ -505,7 +513,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .hskreq = IMX8M_GPU_HSK_PWRDNREQN, .hskack = IMX8M_GPU_HSK_PWRDNACKN, }, - .pgc = IMX8M_PGC_GPU, + .pgc = BIT(IMX8M_PGC_GPU), }, [IMX8M_POWER_DOMAIN_VPU] = { @@ -518,7 +526,8 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .hskreq = IMX8M_VPU_HSK_PWRDNREQN, .hskack = IMX8M_VPU_HSK_PWRDNACKN, }, - .pgc = IMX8M_PGC_VPU, + .pgc = BIT(IMX8M_PGC_VPU), + .keep_clocks = true, }, [IMX8M_POWER_DOMAIN_DISP] = { @@ -531,7 +540,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .hskreq = IMX8M_DISP_HSK_PWRDNREQN, .hskack = IMX8M_DISP_HSK_PWRDNACKN, }, - .pgc = IMX8M_PGC_DISP, + .pgc = BIT(IMX8M_PGC_DISP), }, [IMX8M_POWER_DOMAIN_MIPI_CSI1] = { @@ -542,7 +551,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .pxx = IMX8M_MIPI_CSI1_SW_Pxx_REQ, .map = IMX8M_MIPI_CSI1_A53_DOMAIN, }, - .pgc = IMX8M_PGC_MIPI_CSI1, + .pgc = BIT(IMX8M_PGC_MIPI_CSI1), }, [IMX8M_POWER_DOMAIN_MIPI_CSI2] = { @@ -553,7 +562,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .pxx = IMX8M_MIPI_CSI2_SW_Pxx_REQ, .map = IMX8M_MIPI_CSI2_A53_DOMAIN, }, - .pgc = IMX8M_PGC_MIPI_CSI2, + .pgc = BIT(IMX8M_PGC_MIPI_CSI2), }, [IMX8M_POWER_DOMAIN_PCIE2] = { @@ -564,7 +573,7 @@ static const struct imx_pgc_domain imx8m_pgc_domains[] = { .pxx = IMX8M_PCIE2_SW_Pxx_REQ, .map = IMX8M_PCIE2_A53_DOMAIN, }, - .pgc = IMX8M_PGC_PCIE2, + .pgc = BIT(IMX8M_PGC_PCIE2), }, }; @@ -617,6 +626,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .hskreq = IMX8MM_HSIO_HSK_PWRDNREQN, .hskack = IMX8MM_HSIO_HSK_PWRDNACKN, }, + .keep_clocks = true, }, [IMX8MM_POWER_DOMAIN_PCIE] = { @@ -627,7 +637,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .pxx = IMX8MM_PCIE_SW_Pxx_REQ, .map = IMX8MM_PCIE_A53_DOMAIN, }, - .pgc = IMX8MM_PGC_PCIE, + .pgc = BIT(IMX8MM_PGC_PCIE), }, [IMX8MM_POWER_DOMAIN_OTG1] = { @@ -638,7 +648,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .pxx = IMX8MM_OTG1_SW_Pxx_REQ, .map = IMX8MM_OTG1_A53_DOMAIN, }, - .pgc = IMX8MM_PGC_OTG1, + .pgc = BIT(IMX8MM_PGC_OTG1), }, [IMX8MM_POWER_DOMAIN_OTG2] = { @@ -649,7 +659,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .pxx = IMX8MM_OTG2_SW_Pxx_REQ, .map = IMX8MM_OTG2_A53_DOMAIN, }, - .pgc = IMX8MM_PGC_OTG2, + .pgc = BIT(IMX8MM_PGC_OTG2), }, [IMX8MM_POWER_DOMAIN_GPUMIX] = { @@ -662,7 +672,8 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .hskreq = IMX8MM_GPUMIX_HSK_PWRDNREQN, .hskack = IMX8MM_GPUMIX_HSK_PWRDNACKN, }, - .pgc = IMX8MM_PGC_GPUMIX, + .pgc = BIT(IMX8MM_PGC_GPUMIX), + .keep_clocks = true, }, [IMX8MM_POWER_DOMAIN_GPU] = { @@ -675,7 +686,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .hskreq = IMX8MM_GPU_HSK_PWRDNREQN, .hskack = IMX8MM_GPU_HSK_PWRDNACKN, }, - .pgc = IMX8MM_PGC_GPU2D, + .pgc = BIT(IMX8MM_PGC_GPU2D) | BIT(IMX8MM_PGC_GPU3D), }, [IMX8MM_POWER_DOMAIN_VPUMIX] = { @@ -688,7 +699,8 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .hskreq = IMX8MM_VPUMIX_HSK_PWRDNREQN, .hskack = IMX8MM_VPUMIX_HSK_PWRDNACKN, }, - .pgc = IMX8MM_PGC_VPUMIX, + .pgc = BIT(IMX8MM_PGC_VPUMIX), + .keep_clocks = true, }, [IMX8MM_POWER_DOMAIN_VPUG1] = { @@ -699,7 +711,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .pxx = IMX8MM_VPUG1_SW_Pxx_REQ, .map = IMX8MM_VPUG1_A53_DOMAIN, }, - .pgc = IMX8MM_PGC_VPUG1, + .pgc = BIT(IMX8MM_PGC_VPUG1), }, [IMX8MM_POWER_DOMAIN_VPUG2] = { @@ -710,7 +722,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .pxx = IMX8MM_VPUG2_SW_Pxx_REQ, .map = IMX8MM_VPUG2_A53_DOMAIN, }, - .pgc = IMX8MM_PGC_VPUG2, + .pgc = BIT(IMX8MM_PGC_VPUG2), }, [IMX8MM_POWER_DOMAIN_VPUH1] = { @@ -721,7 +733,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .pxx = IMX8MM_VPUH1_SW_Pxx_REQ, .map = IMX8MM_VPUH1_A53_DOMAIN, }, - .pgc = IMX8MM_PGC_VPUH1, + .pgc = BIT(IMX8MM_PGC_VPUH1), }, [IMX8MM_POWER_DOMAIN_DISPMIX] = { @@ -734,7 +746,8 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .hskreq = IMX8MM_DISPMIX_HSK_PWRDNREQN, .hskack = IMX8MM_DISPMIX_HSK_PWRDNACKN, }, - .pgc = IMX8MM_PGC_DISPMIX, + .pgc = BIT(IMX8MM_PGC_DISPMIX), + .keep_clocks = true, }, [IMX8MM_POWER_DOMAIN_MIPI] = { @@ -745,7 +758,7 @@ static const struct imx_pgc_domain imx8mm_pgc_domains[] = { .pxx = IMX8MM_MIPI_SW_Pxx_REQ, .map = IMX8MM_MIPI_A53_DOMAIN, }, - .pgc = IMX8MM_PGC_MIPI, + .pgc = BIT(IMX8MM_PGC_MIPI), }, }; @@ -802,6 +815,7 @@ static const struct imx_pgc_domain imx8mn_pgc_domains[] = { .hskreq = IMX8MN_HSIO_HSK_PWRDNREQN, .hskack = IMX8MN_HSIO_HSK_PWRDNACKN, }, + .keep_clocks = true, }, [IMX8MN_POWER_DOMAIN_OTG1] = { @@ -812,7 +826,7 @@ static const struct imx_pgc_domain imx8mn_pgc_domains[] = { .pxx = IMX8MN_OTG1_SW_Pxx_REQ, .map = IMX8MN_OTG1_A53_DOMAIN, }, - .pgc = IMX8MN_PGC_OTG1, + .pgc = BIT(IMX8MN_PGC_OTG1), }, [IMX8MN_POWER_DOMAIN_GPUMIX] = { @@ -825,7 +839,7 @@ static const struct imx_pgc_domain imx8mn_pgc_domains[] = { .hskreq = IMX8MN_GPUMIX_HSK_PWRDNREQN, .hskack = IMX8MN_GPUMIX_HSK_PWRDNACKN, }, - .pgc = IMX8MN_PGC_GPUMIX, + .pgc = BIT(IMX8MN_PGC_GPUMIX), }, }; @@ -894,6 +908,10 @@ static int imx_pgc_domain_probe(struct platform_device *pdev) goto out_domain_unmap; } + if (IS_ENABLED(CONFIG_LOCKDEP) && + of_property_read_bool(domain->dev->of_node, "power-domains")) + lockdep_set_subclass(&domain->genpd.mlock, 1); + ret = of_genpd_add_provider_simple(domain->dev->of_node, &domain->genpd); if (ret) { @@ -930,6 +948,36 @@ static int imx_pgc_domain_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int imx_pgc_domain_suspend(struct device *dev) +{ + int ret; + + /* + * This may look strange, but is done so the generic PM_SLEEP code + * can power down our domain and more importantly power it up again + * after resume, without tripping over our usage of runtime PM to + * power up/down the nested domains. + */ + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + return ret; + } + + return 0; +} + +static int imx_pgc_domain_resume(struct device *dev) +{ + return pm_runtime_put(dev); +} +#endif + +static const struct dev_pm_ops imx_pgc_domain_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx_pgc_domain_suspend, imx_pgc_domain_resume) +}; + static const struct platform_device_id imx_pgc_domain_id[] = { { "imx-pgc-domain", }, { }, @@ -938,6 +986,7 @@ static const struct platform_device_id imx_pgc_domain_id[] = { static struct platform_driver imx_pgc_domain_driver = { .driver = { .name = "imx-pgc", + .pm = &imx_pgc_domain_pm_ops, }, .probe = imx_pgc_domain_probe, .remove = imx_pgc_domain_remove, @@ -986,6 +1035,9 @@ static int imx_gpcv2_probe(struct platform_device *pdev) struct imx_pgc_domain *domain; u32 domain_index; + if (!of_device_is_available(np)) + continue; + ret = of_property_read_u32(np, "reg", &domain_index); if (ret) { dev_err(dev, "Failed to read 'reg' property\n"); diff --git a/drivers/soc/imx/imx8m-blk-ctrl.c b/drivers/soc/imx/imx8m-blk-ctrl.c new file mode 100644 index 000000000000..519b3651d1d9 --- /dev/null +++ b/drivers/soc/imx/imx8m-blk-ctrl.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Copyright 2021 Pengutronix, Lucas Stach <kernel@pengutronix.de> + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/clk.h> + +#include <dt-bindings/power/imx8mm-power.h> + +#define BLK_SFT_RSTN 0x0 +#define BLK_CLK_EN 0x4 + +struct imx8m_blk_ctrl_domain; + +struct imx8m_blk_ctrl { + struct device *dev; + struct notifier_block power_nb; + struct device *bus_power_dev; + struct regmap *regmap; + struct imx8m_blk_ctrl_domain *domains; + struct genpd_onecell_data onecell_data; +}; + +struct imx8m_blk_ctrl_domain_data { + const char *name; + const char * const *clk_names; + int num_clks; + const char *gpc_name; + u32 rst_mask; + u32 clk_mask; +}; + +#define DOMAIN_MAX_CLKS 3 + +struct imx8m_blk_ctrl_domain { + struct generic_pm_domain genpd; + const struct imx8m_blk_ctrl_domain_data *data; + struct clk_bulk_data clks[DOMAIN_MAX_CLKS]; + struct device *power_dev; + struct imx8m_blk_ctrl *bc; +}; + +struct imx8m_blk_ctrl_data { + int max_reg; + notifier_fn_t power_notifier_fn; + const struct imx8m_blk_ctrl_domain_data *domains; + int num_domains; +}; + +static inline struct imx8m_blk_ctrl_domain * +to_imx8m_blk_ctrl_domain(struct generic_pm_domain *genpd) +{ + return container_of(genpd, struct imx8m_blk_ctrl_domain, genpd); +} + +static int imx8m_blk_ctrl_power_on(struct generic_pm_domain *genpd) +{ + struct imx8m_blk_ctrl_domain *domain = to_imx8m_blk_ctrl_domain(genpd); + const struct imx8m_blk_ctrl_domain_data *data = domain->data; + struct imx8m_blk_ctrl *bc = domain->bc; + int ret; + + /* make sure bus domain is awake */ + ret = pm_runtime_get_sync(bc->bus_power_dev); + if (ret < 0) { + pm_runtime_put_noidle(bc->bus_power_dev); + dev_err(bc->dev, "failed to power up bus domain\n"); + return ret; + } + + /* put devices into reset */ + regmap_clear_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask); + + /* enable upstream and blk-ctrl clocks to allow reset to propagate */ + ret = clk_bulk_prepare_enable(data->num_clks, domain->clks); + if (ret) { + dev_err(bc->dev, "failed to enable clocks\n"); + goto bus_put; + } + regmap_set_bits(bc->regmap, BLK_CLK_EN, data->clk_mask); + + /* power up upstream GPC domain */ + ret = pm_runtime_get_sync(domain->power_dev); + if (ret < 0) { + dev_err(bc->dev, "failed to power up peripheral domain\n"); + goto clk_disable; + } + + /* wait for reset to propagate */ + udelay(5); + + /* release reset */ + regmap_set_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask); + + /* disable upstream clocks */ + clk_bulk_disable_unprepare(data->num_clks, domain->clks); + + return 0; + +clk_disable: + clk_bulk_disable_unprepare(data->num_clks, domain->clks); +bus_put: + pm_runtime_put(bc->bus_power_dev); + + return ret; +} + +static int imx8m_blk_ctrl_power_off(struct generic_pm_domain *genpd) +{ + struct imx8m_blk_ctrl_domain *domain = to_imx8m_blk_ctrl_domain(genpd); + const struct imx8m_blk_ctrl_domain_data *data = domain->data; + struct imx8m_blk_ctrl *bc = domain->bc; + + /* put devices into reset and disable clocks */ + regmap_clear_bits(bc->regmap, BLK_SFT_RSTN, data->rst_mask); + regmap_clear_bits(bc->regmap, BLK_CLK_EN, data->clk_mask); + + /* power down upstream GPC domain */ + pm_runtime_put(domain->power_dev); + + /* allow bus domain to suspend */ + pm_runtime_put(bc->bus_power_dev); + + return 0; +} + +static struct generic_pm_domain * +imx8m_blk_ctrl_xlate(struct of_phandle_args *args, void *data) +{ + struct genpd_onecell_data *onecell_data = data; + unsigned int index = args->args[0]; + + if (args->args_count != 1 || + index >= onecell_data->num_domains) + return ERR_PTR(-EINVAL); + + return onecell_data->domains[index]; +} + +static struct lock_class_key blk_ctrl_genpd_lock_class; + +static int imx8m_blk_ctrl_probe(struct platform_device *pdev) +{ + const struct imx8m_blk_ctrl_data *bc_data; + struct device *dev = &pdev->dev; + struct imx8m_blk_ctrl *bc; + void __iomem *base; + int i, ret; + + struct regmap_config regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + }; + + bc = devm_kzalloc(dev, sizeof(*bc), GFP_KERNEL); + if (!bc) + return -ENOMEM; + + bc->dev = dev; + + bc_data = of_device_get_match_data(dev); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + regmap_config.max_register = bc_data->max_reg; + bc->regmap = devm_regmap_init_mmio(dev, base, ®map_config); + if (IS_ERR(bc->regmap)) + return dev_err_probe(dev, PTR_ERR(bc->regmap), + "failed to init regmap\n"); + + bc->domains = devm_kcalloc(dev, bc_data->num_domains, + sizeof(struct imx8m_blk_ctrl_domain), + GFP_KERNEL); + if (!bc->domains) + return -ENOMEM; + + bc->onecell_data.num_domains = bc_data->num_domains; + bc->onecell_data.xlate = imx8m_blk_ctrl_xlate; + bc->onecell_data.domains = + devm_kcalloc(dev, bc_data->num_domains, + sizeof(struct generic_pm_domain *), GFP_KERNEL); + if (!bc->onecell_data.domains) + return -ENOMEM; + + bc->bus_power_dev = genpd_dev_pm_attach_by_name(dev, "bus"); + if (IS_ERR(bc->bus_power_dev)) + return dev_err_probe(dev, PTR_ERR(bc->bus_power_dev), + "failed to attach power domain\n"); + + for (i = 0; i < bc_data->num_domains; i++) { + const struct imx8m_blk_ctrl_domain_data *data = &bc_data->domains[i]; + struct imx8m_blk_ctrl_domain *domain = &bc->domains[i]; + int j; + + domain->data = data; + + for (j = 0; j < data->num_clks; j++) + domain->clks[j].id = data->clk_names[j]; + + ret = devm_clk_bulk_get(dev, data->num_clks, domain->clks); + if (ret) { + dev_err_probe(dev, ret, "failed to get clock\n"); + goto cleanup_pds; + } + + domain->power_dev = + dev_pm_domain_attach_by_name(dev, data->gpc_name); + if (IS_ERR(domain->power_dev)) { + dev_err_probe(dev, PTR_ERR(domain->power_dev), + "failed to attach power domain\n"); + ret = PTR_ERR(domain->power_dev); + goto cleanup_pds; + } + + domain->genpd.name = data->name; + domain->genpd.power_on = imx8m_blk_ctrl_power_on; + domain->genpd.power_off = imx8m_blk_ctrl_power_off; + domain->bc = bc; + + ret = pm_genpd_init(&domain->genpd, NULL, true); + if (ret) { + dev_err_probe(dev, ret, "failed to init power domain\n"); + dev_pm_domain_detach(domain->power_dev, true); + goto cleanup_pds; + } + + /* + * We use runtime PM to trigger power on/off of the upstream GPC + * domain, as a strict hierarchical parent/child power domain + * setup doesn't allow us to meet the sequencing requirements. + * This means we have nested locking of genpd locks, without the + * nesting being visible at the genpd level, so we need a + * separate lock class to make lockdep aware of the fact that + * this are separate domain locks that can be nested without a + * self-deadlock. + */ + lockdep_set_class(&domain->genpd.mlock, + &blk_ctrl_genpd_lock_class); + + bc->onecell_data.domains[i] = &domain->genpd; + } + + ret = of_genpd_add_provider_onecell(dev->of_node, &bc->onecell_data); + if (ret) { + dev_err_probe(dev, ret, "failed to add power domain provider\n"); + goto cleanup_pds; + } + + bc->power_nb.notifier_call = bc_data->power_notifier_fn; + ret = dev_pm_genpd_add_notifier(bc->bus_power_dev, &bc->power_nb); + if (ret) { + dev_err_probe(dev, ret, "failed to add power notifier\n"); + goto cleanup_provider; + } + + dev_set_drvdata(dev, bc); + + return 0; + +cleanup_provider: + of_genpd_del_provider(dev->of_node); +cleanup_pds: + for (i--; i >= 0; i--) { + pm_genpd_remove(&bc->domains[i].genpd); + dev_pm_domain_detach(bc->domains[i].power_dev, true); + } + + dev_pm_domain_detach(bc->bus_power_dev, true); + + return ret; +} + +static int imx8m_blk_ctrl_remove(struct platform_device *pdev) +{ + struct imx8m_blk_ctrl *bc = dev_get_drvdata(&pdev->dev); + int i; + + of_genpd_del_provider(pdev->dev.of_node); + + for (i = 0; bc->onecell_data.num_domains; i++) { + struct imx8m_blk_ctrl_domain *domain = &bc->domains[i]; + + pm_genpd_remove(&domain->genpd); + dev_pm_domain_detach(domain->power_dev, true); + } + + dev_pm_genpd_remove_notifier(bc->bus_power_dev); + + dev_pm_domain_detach(bc->bus_power_dev, true); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int imx8m_blk_ctrl_suspend(struct device *dev) +{ + struct imx8m_blk_ctrl *bc = dev_get_drvdata(dev); + int ret, i; + + /* + * This may look strange, but is done so the generic PM_SLEEP code + * can power down our domains and more importantly power them up again + * after resume, without tripping over our usage of runtime PM to + * control the upstream GPC domains. Things happen in the right order + * in the system suspend/resume paths due to the device parent/child + * hierarchy. + */ + ret = pm_runtime_get_sync(bc->bus_power_dev); + if (ret < 0) { + pm_runtime_put_noidle(bc->bus_power_dev); + return ret; + } + + for (i = 0; i < bc->onecell_data.num_domains; i++) { + struct imx8m_blk_ctrl_domain *domain = &bc->domains[i]; + + ret = pm_runtime_get_sync(domain->power_dev); + if (ret < 0) { + pm_runtime_put_noidle(domain->power_dev); + goto out_fail; + } + } + + return 0; + +out_fail: + for (i--; i >= 0; i--) + pm_runtime_put(bc->domains[i].power_dev); + + pm_runtime_put(bc->bus_power_dev); + + return ret; +} + +static int imx8m_blk_ctrl_resume(struct device *dev) +{ + struct imx8m_blk_ctrl *bc = dev_get_drvdata(dev); + int i; + + for (i = 0; i < bc->onecell_data.num_domains; i++) + pm_runtime_put(bc->domains[i].power_dev); + + pm_runtime_put(bc->bus_power_dev); + + return 0; +} +#endif + +static const struct dev_pm_ops imx8m_blk_ctrl_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(imx8m_blk_ctrl_suspend, imx8m_blk_ctrl_resume) +}; + +static int imx8mm_vpu_power_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl, + power_nb); + + if (action != GENPD_NOTIFY_ON && action != GENPD_NOTIFY_PRE_OFF) + return NOTIFY_OK; + + /* + * The ADB in the VPUMIX domain has no separate reset and clock + * enable bits, but is ungated together with the VPU clocks. To + * allow the handshake with the GPC to progress we put the VPUs + * in reset and ungate the clocks. + */ + regmap_clear_bits(bc->regmap, BLK_SFT_RSTN, BIT(0) | BIT(1) | BIT(2)); + regmap_set_bits(bc->regmap, BLK_CLK_EN, BIT(0) | BIT(1) | BIT(2)); + + if (action == GENPD_NOTIFY_ON) { + /* + * On power up we have no software backchannel to the GPC to + * wait for the ADB handshake to happen, so we just delay for a + * bit. On power down the GPC driver waits for the handshake. + */ + udelay(5); + + /* set "fuse" bits to enable the VPUs */ + regmap_set_bits(bc->regmap, 0x8, 0xffffffff); + regmap_set_bits(bc->regmap, 0xc, 0xffffffff); + regmap_set_bits(bc->regmap, 0x10, 0xffffffff); + regmap_set_bits(bc->regmap, 0x14, 0xffffffff); + } + + return NOTIFY_OK; +} + +static const struct imx8m_blk_ctrl_domain_data imx8mm_vpu_blk_ctl_domain_data[] = { + [IMX8MM_VPUBLK_PD_G1] = { + .name = "vpublk-g1", + .clk_names = (const char *[]){ "g1", }, + .num_clks = 1, + .gpc_name = "g1", + .rst_mask = BIT(1), + .clk_mask = BIT(1), + }, + [IMX8MM_VPUBLK_PD_G2] = { + .name = "vpublk-g2", + .clk_names = (const char *[]){ "g2", }, + .num_clks = 1, + .gpc_name = "g2", + .rst_mask = BIT(0), + .clk_mask = BIT(0), + }, + [IMX8MM_VPUBLK_PD_H1] = { + .name = "vpublk-h1", + .clk_names = (const char *[]){ "h1", }, + .num_clks = 1, + .gpc_name = "h1", + .rst_mask = BIT(2), + .clk_mask = BIT(2), + }, +}; + +static const struct imx8m_blk_ctrl_data imx8mm_vpu_blk_ctl_dev_data = { + .max_reg = 0x18, + .power_notifier_fn = imx8mm_vpu_power_notifier, + .domains = imx8mm_vpu_blk_ctl_domain_data, + .num_domains = ARRAY_SIZE(imx8mm_vpu_blk_ctl_domain_data), +}; + +static int imx8mm_disp_power_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct imx8m_blk_ctrl *bc = container_of(nb, struct imx8m_blk_ctrl, + power_nb); + + if (action != GENPD_NOTIFY_ON && action != GENPD_NOTIFY_PRE_OFF) + return NOTIFY_OK; + + /* Enable bus clock and deassert bus reset */ + regmap_set_bits(bc->regmap, BLK_CLK_EN, BIT(12)); + regmap_set_bits(bc->regmap, BLK_SFT_RSTN, BIT(6)); + + /* + * On power up we have no software backchannel to the GPC to + * wait for the ADB handshake to happen, so we just delay for a + * bit. On power down the GPC driver waits for the handshake. + */ + if (action == GENPD_NOTIFY_ON) + udelay(5); + + + return NOTIFY_OK; +} + +static const struct imx8m_blk_ctrl_domain_data imx8mm_disp_blk_ctl_domain_data[] = { + [IMX8MM_DISPBLK_PD_CSI_BRIDGE] = { + .name = "dispblk-csi-bridge", + .clk_names = (const char *[]){ "csi-bridge-axi", "csi-bridge-apb", + "csi-bridge-core", }, + .num_clks = 3, + .gpc_name = "csi-bridge", + .rst_mask = BIT(0) | BIT(1) | BIT(2), + .clk_mask = BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5), + }, + [IMX8MM_DISPBLK_PD_LCDIF] = { + .name = "dispblk-lcdif", + .clk_names = (const char *[]){ "lcdif-axi", "lcdif-apb", "lcdif-pix", }, + .num_clks = 3, + .gpc_name = "lcdif", + .clk_mask = BIT(6) | BIT(7), + }, + [IMX8MM_DISPBLK_PD_MIPI_DSI] = { + .name = "dispblk-mipi-dsi", + .clk_names = (const char *[]){ "dsi-pclk", "dsi-ref", }, + .num_clks = 2, + .gpc_name = "mipi-dsi", + .rst_mask = BIT(5), + .clk_mask = BIT(8) | BIT(9), + }, + [IMX8MM_DISPBLK_PD_MIPI_CSI] = { + .name = "dispblk-mipi-csi", + .clk_names = (const char *[]){ "csi-aclk", "csi-pclk" }, + .num_clks = 2, + .gpc_name = "mipi-csi", + .rst_mask = BIT(3) | BIT(4), + .clk_mask = BIT(10) | BIT(11), + }, +}; + +static const struct imx8m_blk_ctrl_data imx8mm_disp_blk_ctl_dev_data = { + .max_reg = 0x2c, + .power_notifier_fn = imx8mm_disp_power_notifier, + .domains = imx8mm_disp_blk_ctl_domain_data, + .num_domains = ARRAY_SIZE(imx8mm_disp_blk_ctl_domain_data), +}; + +static const struct of_device_id imx8m_blk_ctrl_of_match[] = { + { + .compatible = "fsl,imx8mm-vpu-blk-ctrl", + .data = &imx8mm_vpu_blk_ctl_dev_data + }, { + .compatible = "fsl,imx8mm-disp-blk-ctrl", + .data = &imx8mm_disp_blk_ctl_dev_data + } ,{ + /* Sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, imx8m_blk_ctrl_of_match); + +static struct platform_driver imx8m_blk_ctrl_driver = { + .probe = imx8m_blk_ctrl_probe, + .remove = imx8m_blk_ctrl_remove, + .driver = { + .name = "imx8m-blk-ctrl", + .pm = &imx8m_blk_ctrl_pm_ops, + .of_match_table = imx8m_blk_ctrl_of_match, + }, +}; +module_platform_driver(imx8m_blk_ctrl_driver); diff --git a/drivers/soc/mediatek/mt8192-mmsys.h b/drivers/soc/mediatek/mt8192-mmsys.h new file mode 100644 index 000000000000..6f0a57044a7b --- /dev/null +++ b/drivers/soc/mediatek/mt8192-mmsys.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_MEDIATEK_MT8192_MMSYS_H +#define __SOC_MEDIATEK_MT8192_MMSYS_H + +#define MT8192_MMSYS_OVL_MOUT_EN 0xf04 +#define MT8192_DISP_OVL1_2L_MOUT_EN 0xf08 +#define MT8192_DISP_OVL0_2L_MOUT_EN 0xf18 +#define MT8192_DISP_OVL0_MOUT_EN 0xf1c +#define MT8192_DISP_RDMA0_SEL_IN 0xf2c +#define MT8192_DISP_RDMA0_SOUT_SEL 0xf30 +#define MT8192_DISP_CCORR0_SOUT_SEL 0xf34 +#define MT8192_DISP_AAL0_SEL_IN 0xf38 +#define MT8192_DISP_DITHER0_MOUT_EN 0xf3c +#define MT8192_DISP_DSI0_SEL_IN 0xf40 +#define MT8192_DISP_OVL2_2L_MOUT_EN 0xf4c + +#define MT8192_DISP_OVL0_GO_BLEND BIT(0) +#define MT8192_DITHER0_MOUT_IN_DSI0 BIT(0) +#define MT8192_OVL0_MOUT_EN_DISP_RDMA0 BIT(0) +#define MT8192_OVL2_2L_MOUT_EN_RDMA4 BIT(0) +#define MT8192_DISP_OVL0_GO_BG BIT(1) +#define MT8192_DISP_OVL0_2L_GO_BLEND BIT(2) +#define MT8192_DISP_OVL0_2L_GO_BG BIT(3) +#define MT8192_OVL1_2L_MOUT_EN_RDMA1 BIT(4) +#define MT8192_OVL0_MOUT_EN_OVL0_2L BIT(4) +#define MT8192_RDMA0_SEL_IN_OVL0_2L 0x3 +#define MT8192_RDMA0_SOUT_COLOR0 0x1 +#define MT8192_CCORR0_SOUT_AAL0 0x1 +#define MT8192_AAL0_SEL_IN_CCORR0 0x1 +#define MT8192_DSI0_SEL_IN_DITHER0 0x1 + +static const struct mtk_mmsys_routes mmsys_mt8192_routing_table[] = { + { + DDP_COMPONENT_OVL_2L0, DDP_COMPONENT_RDMA0, + MT8192_DISP_OVL0_2L_MOUT_EN, MT8192_OVL0_MOUT_EN_DISP_RDMA0, + MT8192_OVL0_MOUT_EN_DISP_RDMA0 + }, { + DDP_COMPONENT_OVL_2L2, DDP_COMPONENT_RDMA4, + MT8192_DISP_OVL2_2L_MOUT_EN, MT8192_OVL2_2L_MOUT_EN_RDMA4, + MT8192_OVL2_2L_MOUT_EN_RDMA4 + }, { + DDP_COMPONENT_DITHER, DDP_COMPONENT_DSI0, + MT8192_DISP_DITHER0_MOUT_EN, MT8192_DITHER0_MOUT_IN_DSI0, + MT8192_DITHER0_MOUT_IN_DSI0 + }, { + DDP_COMPONENT_OVL_2L0, DDP_COMPONENT_RDMA0, + MT8192_DISP_RDMA0_SEL_IN, MT8192_RDMA0_SEL_IN_OVL0_2L, + MT8192_RDMA0_SEL_IN_OVL0_2L + }, { + DDP_COMPONENT_CCORR, DDP_COMPONENT_AAL0, + MT8192_DISP_AAL0_SEL_IN, MT8192_AAL0_SEL_IN_CCORR0, + MT8192_AAL0_SEL_IN_CCORR0 + }, { + DDP_COMPONENT_DITHER, DDP_COMPONENT_DSI0, + MT8192_DISP_DSI0_SEL_IN, MT8192_DSI0_SEL_IN_DITHER0 + }, { + DDP_COMPONENT_RDMA0, DDP_COMPONENT_COLOR0, + MT8192_DISP_RDMA0_SOUT_SEL, MT8192_RDMA0_SOUT_COLOR0, + MT8192_RDMA0_SOUT_COLOR0 + }, { + DDP_COMPONENT_CCORR, DDP_COMPONENT_AAL0, + MT8192_DISP_CCORR0_SOUT_SEL, MT8192_CCORR0_SOUT_AAL0, + MT8192_CCORR0_SOUT_AAL0 + }, { + DDP_COMPONENT_OVL0, DDP_COMPONENT_OVL_2L0, + MT8192_MMSYS_OVL_MOUT_EN, MT8192_DISP_OVL0_GO_BG, + MT8192_DISP_OVL0_GO_BG + }, { + DDP_COMPONENT_OVL_2L0, DDP_COMPONENT_RDMA0, + MT8192_MMSYS_OVL_MOUT_EN, MT8192_DISP_OVL0_2L_GO_BLEND, + MT8192_DISP_OVL0_2L_GO_BLEND + } +}; + +#endif /* __SOC_MEDIATEK_MT8192_MMSYS_H */ diff --git a/drivers/soc/mediatek/mtk-mmsys.c b/drivers/soc/mediatek/mtk-mmsys.c index a78e88f27b62..1e448f1ffefb 100644 --- a/drivers/soc/mediatek/mtk-mmsys.c +++ b/drivers/soc/mediatek/mtk-mmsys.c @@ -4,15 +4,18 @@ * Author: James Liao <jamesjj.liao@mediatek.com> */ +#include <linux/delay.h> #include <linux/device.h> #include <linux/io.h> #include <linux/of_device.h> #include <linux/platform_device.h> +#include <linux/reset-controller.h> #include <linux/soc/mediatek/mtk-mmsys.h> #include "mtk-mmsys.h" #include "mt8167-mmsys.h" #include "mt8183-mmsys.h" +#include "mt8192-mmsys.h" #include "mt8365-mmsys.h" static const struct mtk_mmsys_driver_data mt2701_mmsys_driver_data = { @@ -53,6 +56,12 @@ static const struct mtk_mmsys_driver_data mt8183_mmsys_driver_data = { .num_routes = ARRAY_SIZE(mmsys_mt8183_routing_table), }; +static const struct mtk_mmsys_driver_data mt8192_mmsys_driver_data = { + .clk_driver = "clk-mt8192-mm", + .routes = mmsys_mt8192_routing_table, + .num_routes = ARRAY_SIZE(mmsys_mt8192_routing_table), +}; + static const struct mtk_mmsys_driver_data mt8365_mmsys_driver_data = { .clk_driver = "clk-mt8365-mm", .routes = mt8365_mmsys_routing_table, @@ -62,6 +71,8 @@ static const struct mtk_mmsys_driver_data mt8365_mmsys_driver_data = { struct mtk_mmsys { void __iomem *regs; const struct mtk_mmsys_driver_data *data; + spinlock_t lock; /* protects mmsys_sw_rst_b reg */ + struct reset_controller_dev rcdev; }; void mtk_mmsys_ddp_connect(struct device *dev, @@ -101,6 +112,58 @@ void mtk_mmsys_ddp_disconnect(struct device *dev, } EXPORT_SYMBOL_GPL(mtk_mmsys_ddp_disconnect); +static int mtk_mmsys_reset_update(struct reset_controller_dev *rcdev, unsigned long id, + bool assert) +{ + struct mtk_mmsys *mmsys = container_of(rcdev, struct mtk_mmsys, rcdev); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&mmsys->lock, flags); + + reg = readl_relaxed(mmsys->regs + MMSYS_SW0_RST_B); + + if (assert) + reg &= ~BIT(id); + else + reg |= BIT(id); + + writel_relaxed(reg, mmsys->regs + MMSYS_SW0_RST_B); + + spin_unlock_irqrestore(&mmsys->lock, flags); + + return 0; +} + +static int mtk_mmsys_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) +{ + return mtk_mmsys_reset_update(rcdev, id, true); +} + +static int mtk_mmsys_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + return mtk_mmsys_reset_update(rcdev, id, false); +} + +static int mtk_mmsys_reset(struct reset_controller_dev *rcdev, unsigned long id) +{ + int ret; + + ret = mtk_mmsys_reset_assert(rcdev, id); + if (ret) + return ret; + + usleep_range(1000, 1100); + + return mtk_mmsys_reset_deassert(rcdev, id); +} + +static const struct reset_control_ops mtk_mmsys_reset_ops = { + .assert = mtk_mmsys_reset_assert, + .deassert = mtk_mmsys_reset_deassert, + .reset = mtk_mmsys_reset, +}; + static int mtk_mmsys_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -120,6 +183,18 @@ static int mtk_mmsys_probe(struct platform_device *pdev) return ret; } + spin_lock_init(&mmsys->lock); + + mmsys->rcdev.owner = THIS_MODULE; + mmsys->rcdev.nr_resets = 32; + mmsys->rcdev.ops = &mtk_mmsys_reset_ops; + mmsys->rcdev.of_node = pdev->dev.of_node; + ret = devm_reset_controller_register(&pdev->dev, &mmsys->rcdev); + if (ret) { + dev_err(&pdev->dev, "Couldn't register mmsys reset controller: %d\n", ret); + return ret; + } + mmsys->data = of_device_get_match_data(&pdev->dev); platform_set_drvdata(pdev, mmsys); @@ -168,6 +243,10 @@ static const struct of_device_id of_match_mtk_mmsys[] = { .data = &mt8183_mmsys_driver_data, }, { + .compatible = "mediatek,mt8192-mmsys", + .data = &mt8192_mmsys_driver_data, + }, + { .compatible = "mediatek,mt8365-mmsys", .data = &mt8365_mmsys_driver_data, }, diff --git a/drivers/soc/mediatek/mtk-mmsys.h b/drivers/soc/mediatek/mtk-mmsys.h index 9e2b81bd38db..8b0ed05117ea 100644 --- a/drivers/soc/mediatek/mtk-mmsys.h +++ b/drivers/soc/mediatek/mtk-mmsys.h @@ -78,6 +78,8 @@ #define DSI_SEL_IN_RDMA 0x1 #define DSI_SEL_IN_MASK 0x1 +#define MMSYS_SW0_RST_B 0x140 + struct mtk_mmsys_routes { u32 from_comp; u32 to_comp; diff --git a/drivers/soc/mediatek/mtk-mutex.c b/drivers/soc/mediatek/mtk-mutex.c index 2e4bcc300576..2ca55bb5a8be 100644 --- a/drivers/soc/mediatek/mtk-mutex.c +++ b/drivers/soc/mediatek/mtk-mutex.c @@ -39,6 +39,18 @@ #define MT8167_MUTEX_MOD_DISP_DITHER 15 #define MT8167_MUTEX_MOD_DISP_UFOE 16 +#define MT8192_MUTEX_MOD_DISP_OVL0 0 +#define MT8192_MUTEX_MOD_DISP_OVL0_2L 1 +#define MT8192_MUTEX_MOD_DISP_RDMA0 2 +#define MT8192_MUTEX_MOD_DISP_COLOR0 4 +#define MT8192_MUTEX_MOD_DISP_CCORR0 5 +#define MT8192_MUTEX_MOD_DISP_AAL0 6 +#define MT8192_MUTEX_MOD_DISP_GAMMA0 7 +#define MT8192_MUTEX_MOD_DISP_POSTMASK0 8 +#define MT8192_MUTEX_MOD_DISP_DITHER0 9 +#define MT8192_MUTEX_MOD_DISP_OVL2_2L 16 +#define MT8192_MUTEX_MOD_DISP_RDMA4 17 + #define MT8183_MUTEX_MOD_DISP_RDMA0 0 #define MT8183_MUTEX_MOD_DISP_RDMA1 1 #define MT8183_MUTEX_MOD_DISP_OVL0 9 @@ -214,6 +226,20 @@ static const unsigned int mt8183_mutex_mod[DDP_COMPONENT_ID_MAX] = { [DDP_COMPONENT_WDMA0] = MT8183_MUTEX_MOD_DISP_WDMA0, }; +static const unsigned int mt8192_mutex_mod[DDP_COMPONENT_ID_MAX] = { + [DDP_COMPONENT_AAL0] = MT8192_MUTEX_MOD_DISP_AAL0, + [DDP_COMPONENT_CCORR] = MT8192_MUTEX_MOD_DISP_CCORR0, + [DDP_COMPONENT_COLOR0] = MT8192_MUTEX_MOD_DISP_COLOR0, + [DDP_COMPONENT_DITHER] = MT8192_MUTEX_MOD_DISP_DITHER0, + [DDP_COMPONENT_GAMMA] = MT8192_MUTEX_MOD_DISP_GAMMA0, + [DDP_COMPONENT_POSTMASK0] = MT8192_MUTEX_MOD_DISP_POSTMASK0, + [DDP_COMPONENT_OVL0] = MT8192_MUTEX_MOD_DISP_OVL0, + [DDP_COMPONENT_OVL_2L0] = MT8192_MUTEX_MOD_DISP_OVL0_2L, + [DDP_COMPONENT_OVL_2L2] = MT8192_MUTEX_MOD_DISP_OVL2_2L, + [DDP_COMPONENT_RDMA0] = MT8192_MUTEX_MOD_DISP_RDMA0, + [DDP_COMPONENT_RDMA4] = MT8192_MUTEX_MOD_DISP_RDMA4, +}; + static const unsigned int mt2712_mutex_sof[MUTEX_SOF_DSI3 + 1] = { [MUTEX_SOF_SINGLE_MODE] = MUTEX_SOF_SINGLE_MODE, [MUTEX_SOF_DSI0] = MUTEX_SOF_DSI0, @@ -275,6 +301,13 @@ static const struct mtk_mutex_data mt8183_mutex_driver_data = { .no_clk = true, }; +static const struct mtk_mutex_data mt8192_mutex_driver_data = { + .mutex_mod = mt8192_mutex_mod, + .mutex_sof = mt8183_mutex_sof, + .mutex_mod_reg = MT8183_MUTEX0_MOD0, + .mutex_sof_reg = MT8183_MUTEX0_SOF0, +}; + struct mtk_mutex *mtk_mutex_get(struct device *dev) { struct mtk_mutex_ctx *mtx = dev_get_drvdata(dev); @@ -507,6 +540,8 @@ static const struct of_device_id mutex_driver_dt_match[] = { .data = &mt8173_mutex_driver_data}, { .compatible = "mediatek,mt8183-disp-mutex", .data = &mt8183_mutex_driver_data}, + { .compatible = "mediatek,mt8192-disp-mutex", + .data = &mt8192_mutex_driver_data}, {}, }; MODULE_DEVICE_TABLE(of, mutex_driver_dt_match); diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index bfa2ab5772cf..e718b8735444 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -190,6 +190,25 @@ config QCOM_SOCINFO Say yes here to support the Qualcomm socinfo driver, providing information about the SoC to user space. +config QCOM_SPM + tristate "Qualcomm Subsystem Power Manager (SPM)" + depends on ARCH_QCOM || COMPILE_TEST + select QCOM_SCM + help + Enable the support for the Qualcomm Subsystem Power Manager, used + to manage cores, L2 low power modes and to configure the internal + Adaptive Voltage Scaler parameters, where supported. + +config QCOM_STATS + tristate "Qualcomm Technologies, Inc. (QTI) Sleep stats driver" + depends on (ARCH_QCOM && DEBUG_FS) || COMPILE_TEST + depends on QCOM_SMEM + help + Qualcomm Technologies, Inc. (QTI) Sleep stats driver to read + the shared memory exported by the remote processor related to + various SoC level low power modes statistics and export to debugfs + interface. + config QCOM_WCNSS_CTRL tristate "Qualcomm WCNSS control driver" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index ad675a6593d0..70d5de69fd7b 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -20,6 +20,8 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o +obj-$(CONFIG_QCOM_SPM) += spm.o +obj-$(CONFIG_QCOM_STATS) += qcom_stats.o obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_QCOM_APR) += apr.o obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c index 8a9bfbcd4bb9..82ca12c9328a 100644 --- a/drivers/soc/qcom/apr.c +++ b/drivers/soc/qcom/apr.c @@ -492,12 +492,14 @@ static int of_apr_add_pd_lookups(struct device *dev) 1, &service_path); if (ret < 0) { dev_err(dev, "pdr service path missing: %d\n", ret); + of_node_put(node); return ret; } pds = pdr_add_lookup(apr->pdr, service_name, service_path); if (IS_ERR(pds) && PTR_ERR(pds) != -EALREADY) { dev_err(dev, "pdr add lookup failed: %ld\n", PTR_ERR(pds)); + of_node_put(node); return PTR_ERR(pds); } } diff --git a/drivers/soc/qcom/cpr.c b/drivers/soc/qcom/cpr.c index 4ce8e816154f..1d818a8ba208 100644 --- a/drivers/soc/qcom/cpr.c +++ b/drivers/soc/qcom/cpr.c @@ -1614,7 +1614,6 @@ static void cpr_debugfs_init(struct cpr_drv *drv) static int cpr_probe(struct platform_device *pdev) { - struct resource *res; struct device *dev = &pdev->dev; struct cpr_drv *drv; int irq, ret; @@ -1648,8 +1647,7 @@ static int cpr_probe(struct platform_device *pdev) if (IS_ERR(drv->tcsr)) return PTR_ERR(drv->tcsr); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - drv->base = devm_ioremap_resource(dev, res); + drv->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(drv->base)) return PTR_ERR(drv->base); diff --git a/drivers/soc/qcom/llcc-qcom.c b/drivers/soc/qcom/llcc-qcom.c index 15a36dcab990..6bf2f1d1f2c5 100644 --- a/drivers/soc/qcom/llcc-qcom.c +++ b/drivers/soc/qcom/llcc-qcom.c @@ -115,7 +115,7 @@ static const struct llcc_slice_config sc7280_data[] = { { LLCC_CMPT, 10, 768, 1, 1, 0x3f, 0x0, 0, 0, 0, 1, 0, 0}, { LLCC_GPUHTW, 11, 256, 1, 1, 0x3f, 0x0, 0, 0, 0, 1, 0, 0}, { LLCC_GPU, 12, 512, 1, 0, 0x3f, 0x0, 0, 0, 0, 1, 0, 0}, - { LLCC_MMUHWT, 13, 256, 1, 1, 0x3f, 0x0, 0, 0, 0, 1, 1, 0}, + { LLCC_MMUHWT, 13, 256, 1, 1, 0x3f, 0x0, 0, 0, 0, 0, 1, 0}, { LLCC_MDMPNG, 21, 768, 0, 1, 0x3f, 0x0, 0, 0, 0, 1, 0, 0}, { LLCC_WLHW, 24, 256, 1, 1, 0x3f, 0x0, 0, 0, 0, 1, 0, 0}, { LLCC_MODPE, 29, 64, 1, 1, 0x3f, 0x0, 0, 0, 0, 1, 0, 0}, @@ -142,6 +142,16 @@ static const struct llcc_slice_config sdm845_data[] = { { LLCC_AUDHW, 22, 1024, 1, 1, 0xffc, 0x2, 0, 0, 1, 1, 0 }, }; +static const struct llcc_slice_config sm6350_data[] = { + { LLCC_CPUSS, 1, 768, 1, 0, 0xFFF, 0x0, 0, 0, 0, 0, 1, 1 }, + { LLCC_MDM, 8, 512, 2, 0, 0xFFF, 0x0, 0, 0, 0, 0, 1, 0 }, + { LLCC_GPUHTW, 11, 256, 1, 0, 0xFFF, 0x0, 0, 0, 0, 0, 1, 0 }, + { LLCC_GPU, 12, 512, 1, 0, 0xFFF, 0x0, 0, 0, 0, 0, 1, 0 }, + { LLCC_MDMPNG, 21, 768, 0, 1, 0xFFF, 0x0, 0, 0, 0, 0, 1, 0 }, + { LLCC_NPU, 23, 768, 1, 0, 0xFFF, 0x0, 0, 0, 0, 0, 1, 0 }, + { LLCC_MODPE, 29, 64, 1, 1, 0xFFF, 0x0, 0, 0, 0, 0, 1, 0 }, +}; + static const struct llcc_slice_config sm8150_data[] = { { LLCC_CPUSS, 1, 3072, 1, 1, 0xFFF, 0x0, 0, 0, 0, 1, 1 }, { LLCC_VIDSC0, 2, 512, 2, 1, 0xFFF, 0x0, 0, 0, 0, 1, 0 }, @@ -203,6 +213,11 @@ static const struct qcom_llcc_config sdm845_cfg = { .need_llcc_cfg = false, }; +static const struct qcom_llcc_config sm6350_cfg = { + .sct_data = sm6350_data, + .size = ARRAY_SIZE(sm6350_data), +}; + static const struct qcom_llcc_config sm8150_cfg = { .sct_data = sm8150_data, .size = ARRAY_SIZE(sm8150_data), @@ -626,6 +641,7 @@ static const struct of_device_id qcom_llcc_of_match[] = { { .compatible = "qcom,sc7180-llcc", .data = &sc7180_cfg }, { .compatible = "qcom,sc7280-llcc", .data = &sc7280_cfg }, { .compatible = "qcom,sdm845-llcc", .data = &sdm845_cfg }, + { .compatible = "qcom,sm6350-llcc", .data = &sm6350_cfg }, { .compatible = "qcom,sm8150-llcc", .data = &sm8150_cfg }, { .compatible = "qcom,sm8250-llcc", .data = &sm8250_cfg }, { } diff --git a/drivers/soc/qcom/ocmem.c b/drivers/soc/qcom/ocmem.c index f1875dc31ae2..d2dacbbaafbd 100644 --- a/drivers/soc/qcom/ocmem.c +++ b/drivers/soc/qcom/ocmem.c @@ -300,7 +300,6 @@ static int ocmem_dev_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; unsigned long reg, region_size; int i, j, ret, num_banks; - struct resource *res; struct ocmem *ocmem; if (!qcom_scm_is_available()) @@ -321,8 +320,7 @@ static int ocmem_dev_probe(struct platform_device *pdev) return ret; } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ctrl"); - ocmem->mmio = devm_ioremap_resource(&pdev->dev, res); + ocmem->mmio = devm_platform_ioremap_resource_byname(pdev, "ctrl"); if (IS_ERR(ocmem->mmio)) { dev_err(&pdev->dev, "Failed to ioremap ocmem_ctrl resource\n"); return PTR_ERR(ocmem->mmio); diff --git a/drivers/soc/qcom/pdr_interface.c b/drivers/soc/qcom/pdr_interface.c index 915d5bc3d46e..fc580a3c4336 100644 --- a/drivers/soc/qcom/pdr_interface.c +++ b/drivers/soc/qcom/pdr_interface.c @@ -131,7 +131,7 @@ static int pdr_register_listener(struct pdr_handle *pdr, return ret; req.enable = enable; - strcpy(req.service_path, pds->service_path); + strscpy(req.service_path, pds->service_path, sizeof(req.service_path)); ret = qmi_send_request(&pdr->notifier_hdl, &pds->addr, &txn, SERVREG_REGISTER_LISTENER_REQ, @@ -257,7 +257,7 @@ static int pdr_send_indack_msg(struct pdr_handle *pdr, struct pdr_service *pds, return ret; req.transaction_id = tid; - strcpy(req.service_path, pds->service_path); + strscpy(req.service_path, pds->service_path, sizeof(req.service_path)); ret = qmi_send_request(&pdr->notifier_hdl, &pds->addr, &txn, SERVREG_SET_ACK_REQ, @@ -406,7 +406,7 @@ static int pdr_locate_service(struct pdr_handle *pdr, struct pdr_service *pds) return -ENOMEM; /* Prepare req message */ - strcpy(req.service_name, pds->service_name); + strscpy(req.service_name, pds->service_name, sizeof(req.service_name)); req.domain_offset_valid = true; req.domain_offset = 0; @@ -531,8 +531,8 @@ struct pdr_service *pdr_add_lookup(struct pdr_handle *pdr, return ERR_PTR(-ENOMEM); pds->service = SERVREG_NOTIFIER_SERVICE; - strcpy(pds->service_name, service_name); - strcpy(pds->service_path, service_path); + strscpy(pds->service_name, service_name, sizeof(pds->service_name)); + strscpy(pds->service_path, service_path, sizeof(pds->service_path)); pds->need_locator_lookup = true; mutex_lock(&pdr->list_lock); @@ -587,7 +587,7 @@ int pdr_restart_pd(struct pdr_handle *pdr, struct pdr_service *pds) break; /* Prepare req message */ - strcpy(req.service_path, pds->service_path); + strscpy(req.service_path, pds->service_path, sizeof(req.service_path)); addr = pds->addr; break; } diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c index 7d649d2cf31e..28a8c0dda66c 100644 --- a/drivers/soc/qcom/qcom-geni-se.c +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -871,7 +871,6 @@ EXPORT_SYMBOL(geni_icc_disable); static int geni_se_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; struct geni_wrapper *wrapper; int ret; @@ -880,8 +879,7 @@ static int geni_se_probe(struct platform_device *pdev) return -ENOMEM; wrapper->dev = dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - wrapper->base = devm_ioremap_resource(dev, res); + wrapper->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(wrapper->base)) return PTR_ERR(wrapper->base); diff --git a/drivers/soc/qcom/qcom_aoss.c b/drivers/soc/qcom/qcom_aoss.c index 536c3e4114fb..34acf58bbb0d 100644 --- a/drivers/soc/qcom/qcom_aoss.c +++ b/drivers/soc/qcom/qcom_aoss.c @@ -2,16 +2,16 @@ /* * Copyright (c) 2019, Linaro Ltd */ -#include <dt-bindings/power/qcom-aoss-qmp.h> #include <linux/clk-provider.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/mailbox_client.h> #include <linux/module.h> +#include <linux/of_platform.h> #include <linux/platform_device.h> -#include <linux/pm_domain.h> #include <linux/thermal.h> #include <linux/slab.h> +#include <linux/soc/qcom/qcom_aoss.h> #define QMP_DESC_MAGIC 0x0 #define QMP_DESC_VERSION 0x4 @@ -64,7 +64,6 @@ struct qmp_cooling_device { * @event: wait_queue for synchronization with the IRQ * @tx_lock: provides synchronization between multiple callers of qmp_send() * @qdss_clk: QDSS clock hw struct - * @pd_data: genpd data * @cooling_devs: thermal cooling devices */ struct qmp { @@ -82,17 +81,9 @@ struct qmp { struct mutex tx_lock; struct clk_hw qdss_clk; - struct genpd_onecell_data pd_data; struct qmp_cooling_device *cooling_devs; }; -struct qmp_pd { - struct qmp *qmp; - struct generic_pm_domain pd; -}; - -#define to_qmp_pd_resource(res) container_of(res, struct qmp_pd, pd) - static void qmp_kick(struct qmp *qmp) { mbox_send_message(qmp->mbox_chan, NULL); @@ -223,11 +214,14 @@ static bool qmp_message_empty(struct qmp *qmp) * * Return: 0 on success, negative errno on failure */ -static int qmp_send(struct qmp *qmp, const void *data, size_t len) +int qmp_send(struct qmp *qmp, const void *data, size_t len) { long time_left; int ret; + if (WARN_ON(IS_ERR_OR_NULL(qmp) || !data)) + return -EINVAL; + if (WARN_ON(len + sizeof(u32) > qmp->size)) return -EINVAL; @@ -261,6 +255,7 @@ static int qmp_send(struct qmp *qmp, const void *data, size_t len) return ret; } +EXPORT_SYMBOL(qmp_send); static int qmp_qdss_clk_prepare(struct clk_hw *hw) { @@ -314,95 +309,6 @@ static void qmp_qdss_clk_remove(struct qmp *qmp) clk_hw_unregister(&qmp->qdss_clk); } -static int qmp_pd_power_toggle(struct qmp_pd *res, bool enable) -{ - char buf[QMP_MSG_LEN] = {}; - - snprintf(buf, sizeof(buf), - "{class: image, res: load_state, name: %s, val: %s}", - res->pd.name, enable ? "on" : "off"); - return qmp_send(res->qmp, buf, sizeof(buf)); -} - -static int qmp_pd_power_on(struct generic_pm_domain *domain) -{ - return qmp_pd_power_toggle(to_qmp_pd_resource(domain), true); -} - -static int qmp_pd_power_off(struct generic_pm_domain *domain) -{ - return qmp_pd_power_toggle(to_qmp_pd_resource(domain), false); -} - -static const char * const sdm845_resources[] = { - [AOSS_QMP_LS_CDSP] = "cdsp", - [AOSS_QMP_LS_LPASS] = "adsp", - [AOSS_QMP_LS_MODEM] = "modem", - [AOSS_QMP_LS_SLPI] = "slpi", - [AOSS_QMP_LS_SPSS] = "spss", - [AOSS_QMP_LS_VENUS] = "venus", -}; - -static int qmp_pd_add(struct qmp *qmp) -{ - struct genpd_onecell_data *data = &qmp->pd_data; - struct device *dev = qmp->dev; - struct qmp_pd *res; - size_t num = ARRAY_SIZE(sdm845_resources); - int ret; - int i; - - res = devm_kcalloc(dev, num, sizeof(*res), GFP_KERNEL); - if (!res) - return -ENOMEM; - - data->domains = devm_kcalloc(dev, num, sizeof(*data->domains), - GFP_KERNEL); - if (!data->domains) - return -ENOMEM; - - for (i = 0; i < num; i++) { - res[i].qmp = qmp; - res[i].pd.name = sdm845_resources[i]; - res[i].pd.power_on = qmp_pd_power_on; - res[i].pd.power_off = qmp_pd_power_off; - - ret = pm_genpd_init(&res[i].pd, NULL, true); - if (ret < 0) { - dev_err(dev, "failed to init genpd\n"); - goto unroll_genpds; - } - - data->domains[i] = &res[i].pd; - } - - data->num_domains = i; - - ret = of_genpd_add_provider_onecell(dev->of_node, data); - if (ret < 0) - goto unroll_genpds; - - return 0; - -unroll_genpds: - for (i--; i >= 0; i--) - pm_genpd_remove(data->domains[i]); - - return ret; -} - -static void qmp_pd_remove(struct qmp *qmp) -{ - struct genpd_onecell_data *data = &qmp->pd_data; - struct device *dev = qmp->dev; - int i; - - of_genpd_del_provider(dev->of_node); - - for (i = 0; i < data->num_domains; i++) - pm_genpd_remove(data->domains[i]); -} - static int qmp_cdev_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { @@ -519,9 +425,53 @@ static void qmp_cooling_devices_remove(struct qmp *qmp) thermal_cooling_device_unregister(qmp->cooling_devs[i].cdev); } +/** + * qmp_get() - get a qmp handle from a device + * @dev: client device pointer + * + * Return: handle to qmp device on success, ERR_PTR() on failure + */ +struct qmp *qmp_get(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + struct qmp *qmp; + + if (!dev || !dev->of_node) + return ERR_PTR(-EINVAL); + + np = of_parse_phandle(dev->of_node, "qcom,qmp", 0); + if (!np) + return ERR_PTR(-ENODEV); + + pdev = of_find_device_by_node(np); + of_node_put(np); + if (!pdev) + return ERR_PTR(-EINVAL); + + qmp = platform_get_drvdata(pdev); + + return qmp ? qmp : ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL(qmp_get); + +/** + * qmp_put() - release a qmp handle + * @qmp: qmp handle obtained from qmp_get() + */ +void qmp_put(struct qmp *qmp) +{ + /* + * Match get_device() inside of_find_device_by_node() in + * qmp_get() + */ + if (!IS_ERR_OR_NULL(qmp)) + put_device(qmp->dev); +} +EXPORT_SYMBOL(qmp_put); + static int qmp_probe(struct platform_device *pdev) { - struct resource *res; struct qmp *qmp; int irq; int ret; @@ -534,8 +484,7 @@ static int qmp_probe(struct platform_device *pdev) init_waitqueue_head(&qmp->event); mutex_init(&qmp->tx_lock); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - qmp->msgram = devm_ioremap_resource(&pdev->dev, res); + qmp->msgram = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(qmp->msgram)) return PTR_ERR(qmp->msgram); @@ -563,10 +512,6 @@ static int qmp_probe(struct platform_device *pdev) if (ret) goto err_close_qmp; - ret = qmp_pd_add(qmp); - if (ret) - goto err_remove_qdss_clk; - ret = qmp_cooling_devices_register(qmp); if (ret) dev_err(&pdev->dev, "failed to register aoss cooling devices\n"); @@ -575,8 +520,6 @@ static int qmp_probe(struct platform_device *pdev) return 0; -err_remove_qdss_clk: - qmp_qdss_clk_remove(qmp); err_close_qmp: qmp_close(qmp); err_free_mbox: @@ -590,7 +533,6 @@ static int qmp_remove(struct platform_device *pdev) struct qmp *qmp = platform_get_drvdata(pdev); qmp_qdss_clk_remove(qmp); - qmp_pd_remove(qmp); qmp_cooling_devices_remove(qmp); qmp_close(qmp); @@ -615,6 +557,7 @@ static struct platform_driver qmp_driver = { .driver = { .name = "qcom_aoss_qmp", .of_match_table = qmp_dt_match, + .suppress_bind_attrs = true, }, .probe = qmp_probe, .remove = qmp_remove, diff --git a/drivers/soc/qcom/qcom_gsbi.c b/drivers/soc/qcom/qcom_gsbi.c index 304afc223a58..290bdefbf28a 100644 --- a/drivers/soc/qcom/qcom_gsbi.c +++ b/drivers/soc/qcom/qcom_gsbi.c @@ -127,7 +127,6 @@ static int gsbi_probe(struct platform_device *pdev) struct device_node *node = pdev->dev.of_node; struct device_node *tcsr_node; const struct of_device_id *match; - struct resource *res; void __iomem *base; struct gsbi_info *gsbi; int i, ret; @@ -139,8 +138,7 @@ static int gsbi_probe(struct platform_device *pdev) if (!gsbi) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); diff --git a/drivers/soc/qcom/qcom_stats.c b/drivers/soc/qcom/qcom_stats.c new file mode 100644 index 000000000000..131d24caabf8 --- /dev/null +++ b/drivers/soc/qcom/qcom_stats.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. + */ + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> + +#include <linux/soc/qcom/smem.h> +#include <clocksource/arm_arch_timer.h> + +#define RPM_DYNAMIC_ADDR 0x14 +#define RPM_DYNAMIC_ADDR_MASK 0xFFFF + +#define STAT_TYPE_OFFSET 0x0 +#define COUNT_OFFSET 0x4 +#define LAST_ENTERED_AT_OFFSET 0x8 +#define LAST_EXITED_AT_OFFSET 0x10 +#define ACCUMULATED_OFFSET 0x18 +#define CLIENT_VOTES_OFFSET 0x20 + +struct subsystem_data { + const char *name; + u32 smem_item; + u32 pid; +}; + +static const struct subsystem_data subsystems[] = { + { "modem", 605, 1 }, + { "wpss", 605, 13 }, + { "adsp", 606, 2 }, + { "cdsp", 607, 5 }, + { "slpi", 608, 3 }, + { "gpu", 609, 0 }, + { "display", 610, 0 }, + { "adsp_island", 613, 2 }, + { "slpi_island", 613, 3 }, +}; + +struct stats_config { + size_t stats_offset; + size_t num_records; + bool appended_stats_avail; + bool dynamic_offset; + bool subsystem_stats_in_smem; +}; + +struct stats_data { + bool appended_stats_avail; + void __iomem *base; +}; + +struct sleep_stats { + u32 stat_type; + u32 count; + u64 last_entered_at; + u64 last_exited_at; + u64 accumulated; +}; + +struct appended_stats { + u32 client_votes; + u32 reserved[3]; +}; + +static void qcom_print_stats(struct seq_file *s, const struct sleep_stats *stat) +{ + u64 accumulated = stat->accumulated; + /* + * If a subsystem is in sleep when reading the sleep stats adjust + * the accumulated sleep duration to show actual sleep time. + */ + if (stat->last_entered_at > stat->last_exited_at) + accumulated += arch_timer_read_counter() - stat->last_entered_at; + + seq_printf(s, "Count: %u\n", stat->count); + seq_printf(s, "Last Entered At: %llu\n", stat->last_entered_at); + seq_printf(s, "Last Exited At: %llu\n", stat->last_exited_at); + seq_printf(s, "Accumulated Duration: %llu\n", accumulated); +} + +static int qcom_subsystem_sleep_stats_show(struct seq_file *s, void *unused) +{ + struct subsystem_data *subsystem = s->private; + struct sleep_stats *stat; + + /* Items are allocated lazily, so lookup pointer each time */ + stat = qcom_smem_get(subsystem->pid, subsystem->smem_item, NULL); + if (IS_ERR(stat)) + return -EIO; + + qcom_print_stats(s, stat); + + return 0; +} + +static int qcom_soc_sleep_stats_show(struct seq_file *s, void *unused) +{ + struct stats_data *d = s->private; + void __iomem *reg = d->base; + struct sleep_stats stat; + + memcpy_fromio(&stat, reg, sizeof(stat)); + qcom_print_stats(s, &stat); + + if (d->appended_stats_avail) { + struct appended_stats votes; + + memcpy_fromio(&votes, reg + CLIENT_VOTES_OFFSET, sizeof(votes)); + seq_printf(s, "Client Votes: %#x\n", votes.client_votes); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(qcom_soc_sleep_stats); +DEFINE_SHOW_ATTRIBUTE(qcom_subsystem_sleep_stats); + +static void qcom_create_soc_sleep_stat_files(struct dentry *root, void __iomem *reg, + struct stats_data *d, + const struct stats_config *config) +{ + char stat_type[sizeof(u32) + 1] = {0}; + size_t stats_offset = config->stats_offset; + u32 offset = 0, type; + int i, j; + + /* + * On RPM targets, stats offset location is dynamic and changes from target + * to target and sometimes from build to build for same target. + * + * In such cases the dynamic address is present at 0x14 offset from base + * address in devicetree. The last 16bits indicates the stats_offset. + */ + if (config->dynamic_offset) { + stats_offset = readl(reg + RPM_DYNAMIC_ADDR); + stats_offset &= RPM_DYNAMIC_ADDR_MASK; + } + + for (i = 0; i < config->num_records; i++) { + d[i].base = reg + offset + stats_offset; + + /* + * Read the low power mode name and create debugfs file for it. + * The names read could be of below, + * (may change depending on low power mode supported). + * For rpmh-sleep-stats: "aosd", "cxsd" and "ddr". + * For rpm-sleep-stats: "vmin" and "vlow". + */ + type = readl(d[i].base); + for (j = 0; j < sizeof(u32); j++) { + stat_type[j] = type & 0xff; + type = type >> 8; + } + strim(stat_type); + debugfs_create_file(stat_type, 0400, root, &d[i], + &qcom_soc_sleep_stats_fops); + + offset += sizeof(struct sleep_stats); + if (d[i].appended_stats_avail) + offset += sizeof(struct appended_stats); + } +} + +static void qcom_create_subsystem_stat_files(struct dentry *root, + const struct stats_config *config) +{ + const struct sleep_stats *stat; + int i; + + if (!config->subsystem_stats_in_smem) + return; + + for (i = 0; i < ARRAY_SIZE(subsystems); i++) { + stat = qcom_smem_get(subsystems[i].pid, subsystems[i].smem_item, NULL); + if (IS_ERR(stat)) + continue; + + debugfs_create_file(subsystems[i].name, 0400, root, (void *)&subsystems[i], + &qcom_subsystem_sleep_stats_fops); + } +} + +static int qcom_stats_probe(struct platform_device *pdev) +{ + void __iomem *reg; + struct dentry *root; + const struct stats_config *config; + struct stats_data *d; + int i; + + config = device_get_match_data(&pdev->dev); + if (!config) + return -ENODEV; + + reg = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); + if (IS_ERR(reg)) + return -ENOMEM; + + d = devm_kcalloc(&pdev->dev, config->num_records, + sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + for (i = 0; i < config->num_records; i++) + d[i].appended_stats_avail = config->appended_stats_avail; + + root = debugfs_create_dir("qcom_stats", NULL); + + qcom_create_subsystem_stat_files(root, config); + qcom_create_soc_sleep_stat_files(root, reg, d, config); + + platform_set_drvdata(pdev, root); + + return 0; +} + +static int qcom_stats_remove(struct platform_device *pdev) +{ + struct dentry *root = platform_get_drvdata(pdev); + + debugfs_remove_recursive(root); + + return 0; +} + +static const struct stats_config rpm_data = { + .stats_offset = 0, + .num_records = 2, + .appended_stats_avail = true, + .dynamic_offset = true, + .subsystem_stats_in_smem = false, +}; + +static const struct stats_config rpmh_data = { + .stats_offset = 0x48, + .num_records = 3, + .appended_stats_avail = false, + .dynamic_offset = false, + .subsystem_stats_in_smem = true, +}; + +static const struct of_device_id qcom_stats_table[] = { + { .compatible = "qcom,rpm-stats", .data = &rpm_data }, + { .compatible = "qcom,rpmh-stats", .data = &rpmh_data }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_stats_table); + +static struct platform_driver qcom_stats = { + .probe = qcom_stats_probe, + .remove = qcom_stats_remove, + .driver = { + .name = "qcom_stats", + .of_match_table = qcom_stats_table, + }, +}; + +static int __init qcom_stats_init(void) +{ + return platform_driver_register(&qcom_stats); +} +late_initcall(qcom_stats_init); + +static void __exit qcom_stats_exit(void) +{ + platform_driver_unregister(&qcom_stats); +} +module_exit(qcom_stats_exit) + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. (QTI) Stats driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index e749a2b285d8..3a12a482f6b2 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -910,7 +910,6 @@ static int rpmh_rsc_probe(struct platform_device *pdev) { struct device_node *dn = pdev->dev.of_node; struct rsc_drv *drv; - struct resource *res; char drv_id[10] = {0}; int ret, irq; u32 solver_config; @@ -941,8 +940,7 @@ static int rpmh_rsc_probe(struct platform_device *pdev) drv->name = dev_name(&pdev->dev); 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); + base = devm_platform_ioremap_resource_byname(pdev, drv_id); if (IS_ERR(base)) return PTR_ERR(base); diff --git a/drivers/soc/qcom/rpmhpd.c b/drivers/soc/qcom/rpmhpd.c index fa209b479ab3..1118345d8824 100644 --- a/drivers/soc/qcom/rpmhpd.c +++ b/drivers/soc/qcom/rpmhpd.c @@ -30,6 +30,7 @@ * @active_only: True if it represents an Active only peer * @corner: current corner * @active_corner: current active corner + * @enable_corner: lowest non-zero corner * @level: An array of level (vlvl) to corner (hlvl) mappings * derived from cmd-db * @level_count: Number of levels supported by the power domain. max @@ -47,6 +48,7 @@ struct rpmhpd { const bool active_only; unsigned int corner; unsigned int active_corner; + unsigned int enable_corner; u32 level[RPMH_ARC_MAX_LEVELS]; size_t level_count; bool enabled; @@ -147,6 +149,21 @@ static const struct rpmhpd_desc sdx55_desc = { .num_pds = ARRAY_SIZE(sdx55_rpmhpds), }; +/* SM6350 RPMH powerdomains */ +static struct rpmhpd *sm6350_rpmhpds[] = { + [SM6350_CX] = &sdm845_cx, + [SM6350_GFX] = &sdm845_gfx, + [SM6350_LCX] = &sdm845_lcx, + [SM6350_LMX] = &sdm845_lmx, + [SM6350_MSS] = &sdm845_mss, + [SM6350_MX] = &sdm845_mx, +}; + +static const struct rpmhpd_desc sm6350_desc = { + .rpmhpds = sm6350_rpmhpds, + .num_pds = ARRAY_SIZE(sm6350_rpmhpds), +}; + /* SM8150 RPMH powerdomains */ static struct rpmhpd sm8150_mmcx_ao; @@ -204,7 +221,7 @@ static const struct rpmhpd_desc sm8250_desc = { static struct rpmhpd sm8350_mxc_ao; static struct rpmhpd sm8350_mxc = { .pd = { .name = "mxc", }, - .peer = &sm8150_mmcx_ao, + .peer = &sm8350_mxc_ao, .res_name = "mxc.lvl", }; @@ -297,6 +314,7 @@ static const struct of_device_id rpmhpd_match_table[] = { { .compatible = "qcom,sc8180x-rpmhpd", .data = &sc8180x_desc }, { .compatible = "qcom,sdm845-rpmhpd", .data = &sdm845_desc }, { .compatible = "qcom,sdx55-rpmhpd", .data = &sdx55_desc}, + { .compatible = "qcom,sm6350-rpmhpd", .data = &sm6350_desc }, { .compatible = "qcom,sm8150-rpmhpd", .data = &sm8150_desc }, { .compatible = "qcom,sm8250-rpmhpd", .data = &sm8250_desc }, { .compatible = "qcom,sm8350-rpmhpd", .data = &sm8350_desc }, @@ -385,13 +403,13 @@ static int rpmhpd_aggregate_corner(struct rpmhpd *pd, unsigned int corner) static int rpmhpd_power_on(struct generic_pm_domain *domain) { struct rpmhpd *pd = domain_to_rpmhpd(domain); - int ret = 0; + unsigned int corner; + int ret; mutex_lock(&rpmhpd_lock); - if (pd->corner) - ret = rpmhpd_aggregate_corner(pd, pd->corner); - + corner = max(pd->corner, pd->enable_corner); + ret = rpmhpd_aggregate_corner(pd, corner); if (!ret) pd->enabled = true; @@ -436,6 +454,10 @@ static int rpmhpd_set_performance_state(struct generic_pm_domain *domain, i--; if (pd->enabled) { + /* Ensure that the domain isn't turn off */ + if (i < pd->enable_corner) + i = pd->enable_corner; + ret = rpmhpd_aggregate_corner(pd, i); if (ret) goto out; @@ -472,6 +494,10 @@ static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd) for (i = 0; i < rpmhpd->level_count; i++) { rpmhpd->level[i] = buf[i]; + /* Remember the first corner with non-zero level */ + if (!rpmhpd->level[rpmhpd->enable_corner] && rpmhpd->level[i]) + rpmhpd->enable_corner = i; + /* * The AUX data may be zero padded. These 0 valued entries at * the end of the map must be ignored. diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c index dbf494e92574..4f69fb9b2e0e 100644 --- a/drivers/soc/qcom/rpmpd.c +++ b/drivers/soc/qcom/rpmpd.c @@ -185,6 +185,29 @@ static const struct rpmpd_desc msm8916_desc = { .max_state = MAX_CORNER_RPMPD_STATE, }; +/* msm8953 RPM Power Domains */ +DEFINE_RPMPD_PAIR(msm8953, vddmd, vddmd_ao, SMPA, LEVEL, 1); +DEFINE_RPMPD_PAIR(msm8953, vddcx, vddcx_ao, SMPA, LEVEL, 2); +DEFINE_RPMPD_PAIR(msm8953, vddmx, vddmx_ao, SMPA, LEVEL, 7); + +DEFINE_RPMPD_VFL(msm8953, vddcx_vfl, SMPA, 2); + +static struct rpmpd *msm8953_rpmpds[] = { + [MSM8953_VDDMD] = &msm8953_vddmd, + [MSM8953_VDDMD_AO] = &msm8953_vddmd_ao, + [MSM8953_VDDCX] = &msm8953_vddcx, + [MSM8953_VDDCX_AO] = &msm8953_vddcx_ao, + [MSM8953_VDDCX_VFL] = &msm8953_vddcx_vfl, + [MSM8953_VDDMX] = &msm8953_vddmx, + [MSM8953_VDDMX_AO] = &msm8953_vddmx_ao, +}; + +static const struct rpmpd_desc msm8953_desc = { + .rpmpds = msm8953_rpmpds, + .num_pds = ARRAY_SIZE(msm8953_rpmpds), + .max_state = RPM_SMD_LEVEL_TURBO, +}; + /* msm8976 RPM Power Domains */ DEFINE_RPMPD_PAIR(msm8976, vddcx, vddcx_ao, SMPA, LEVEL, 2); DEFINE_RPMPD_PAIR(msm8976, vddmx, vddmx_ao, SMPA, LEVEL, 6); @@ -377,6 +400,7 @@ static const struct of_device_id rpmpd_match_table[] = { { .compatible = "qcom,mdm9607-rpmpd", .data = &mdm9607_desc }, { .compatible = "qcom,msm8916-rpmpd", .data = &msm8916_desc }, { .compatible = "qcom,msm8939-rpmpd", .data = &msm8939_desc }, + { .compatible = "qcom,msm8953-rpmpd", .data = &msm8953_desc }, { .compatible = "qcom,msm8976-rpmpd", .data = &msm8976_desc }, { .compatible = "qcom,msm8994-rpmpd", .data = &msm8994_desc }, { .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc }, diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index dfdd4f20f5fd..30dda1af63c8 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -236,6 +236,7 @@ static const struct of_device_id qcom_smd_rpm_of_match[] = { { .compatible = "qcom,rpm-msm8226" }, { .compatible = "qcom,rpm-msm8916" }, { .compatible = "qcom,rpm-msm8936" }, + { .compatible = "qcom,rpm-msm8953" }, { .compatible = "qcom,rpm-msm8974" }, { .compatible = "qcom,rpm-msm8976" }, { .compatible = "qcom,rpm-msm8994" }, @@ -244,6 +245,7 @@ static const struct of_device_id qcom_smd_rpm_of_match[] = { { .compatible = "qcom,rpm-sdm660" }, { .compatible = "qcom,rpm-sm6115" }, { .compatible = "qcom,rpm-sm6125" }, + { .compatible = "qcom,rpm-qcm2290" }, { .compatible = "qcom,rpm-qcs404" }, {} }; diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 4fb5aeeb0843..c7e519bfdc8a 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -9,6 +9,7 @@ #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/of_reserved_mem.h> #include <linux/platform_device.h> #include <linux/sizes.h> #include <linux/slab.h> @@ -240,7 +241,7 @@ static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ * @size: size of the memory region */ struct smem_region { - u32 aux_base; + phys_addr_t aux_base; void __iomem *virt_base; size_t size; }; @@ -499,7 +500,7 @@ static void *qcom_smem_get_global(struct qcom_smem *smem, for (i = 0; i < smem->num_regions; i++) { region = &smem->regions[i]; - if (region->aux_base == aux_base || !aux_base) { + if ((u32)region->aux_base == aux_base || !aux_base) { if (size != NULL) *size = le32_to_cpu(entry->size); return region->virt_base + le32_to_cpu(entry->offset); @@ -664,7 +665,7 @@ phys_addr_t qcom_smem_virt_to_phys(void *p) if (p < region->virt_base + region->size) { u64 offset = p - region->virt_base; - return (phys_addr_t)region->aux_base + offset; + return region->aux_base + offset; } } @@ -863,12 +864,12 @@ qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host) return 0; } -static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, - const char *name, int i) +static int qcom_smem_resolve_mem(struct qcom_smem *smem, const char *name, + struct smem_region *region) { + struct device *dev = smem->dev; struct device_node *np; struct resource r; - resource_size_t size; int ret; np = of_parse_phandle(dev->of_node, name, 0); @@ -881,13 +882,9 @@ static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, of_node_put(np); if (ret) return ret; - size = resource_size(&r); - smem->regions[i].virt_base = devm_ioremap_wc(dev, r.start, size); - if (!smem->regions[i].virt_base) - return -ENOMEM; - smem->regions[i].aux_base = (u32)r.start; - smem->regions[i].size = size; + region->aux_base = r.start; + region->size = resource_size(&r); return 0; } @@ -895,12 +892,14 @@ static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, static int qcom_smem_probe(struct platform_device *pdev) { struct smem_header *header; + struct reserved_mem *rmem; struct qcom_smem *smem; size_t array_size; int num_regions; int hwlock_id; u32 version; int ret; + int i; num_regions = 1; if (of_find_property(pdev->dev.of_node, "qcom,rpm-msg-ram", NULL)) @@ -914,13 +913,35 @@ static int qcom_smem_probe(struct platform_device *pdev) smem->dev = &pdev->dev; smem->num_regions = num_regions; - ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0); - if (ret) - return ret; + rmem = of_reserved_mem_lookup(pdev->dev.of_node); + if (rmem) { + smem->regions[0].aux_base = rmem->base; + smem->regions[0].size = rmem->size; + } else { + /* + * Fall back to the memory-region reference, if we're not a + * reserved-memory node. + */ + ret = qcom_smem_resolve_mem(smem, "memory-region", &smem->regions[0]); + if (ret) + return ret; + } - if (num_regions > 1 && (ret = qcom_smem_map_memory(smem, &pdev->dev, - "qcom,rpm-msg-ram", 1))) - return ret; + if (num_regions > 1) { + ret = qcom_smem_resolve_mem(smem, "qcom,rpm-msg-ram", &smem->regions[1]); + if (ret) + return ret; + } + + for (i = 0; i < num_regions; i++) { + smem->regions[i].virt_base = devm_ioremap_wc(&pdev->dev, + smem->regions[i].aux_base, + smem->regions[i].size); + if (!smem->regions[i].virt_base) { + dev_err(&pdev->dev, "failed to remap %pa\n", &smem->regions[i].aux_base); + return -ENOMEM; + } + } header = smem->regions[0].virt_base; if (le32_to_cpu(header->initialized) != 1 || diff --git a/drivers/soc/qcom/smp2p.c b/drivers/soc/qcom/smp2p.c index 2df488333be9..4a157240f419 100644 --- a/drivers/soc/qcom/smp2p.c +++ b/drivers/soc/qcom/smp2p.c @@ -14,6 +14,7 @@ #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> #include <linux/regmap.h> #include <linux/soc/qcom/smem.h> #include <linux/soc/qcom/smem_state.h> @@ -40,8 +41,11 @@ #define SMP2P_MAX_ENTRY_NAME 16 #define SMP2P_FEATURE_SSR_ACK 0x1 +#define SMP2P_FLAGS_RESTART_DONE_BIT 0 +#define SMP2P_FLAGS_RESTART_ACK_BIT 1 #define SMP2P_MAGIC 0x504d5324 +#define SMP2P_ALL_FEATURES SMP2P_FEATURE_SSR_ACK /** * struct smp2p_smem_item - in memory communication structure @@ -135,6 +139,10 @@ struct qcom_smp2p { unsigned valid_entries; + bool ssr_ack_enabled; + bool ssr_ack; + bool negotiation_done; + unsigned local_pid; unsigned remote_pid; @@ -162,22 +170,53 @@ static void qcom_smp2p_kick(struct qcom_smp2p *smp2p) } } -/** - * qcom_smp2p_intr() - interrupt handler for incoming notifications - * @irq: unused - * @data: smp2p driver context - * - * Handle notifications from the remote side to handle newly allocated entries - * or any changes to the state bits of existing entries. - */ -static irqreturn_t qcom_smp2p_intr(int irq, void *data) +static bool qcom_smp2p_check_ssr(struct qcom_smp2p *smp2p) +{ + struct smp2p_smem_item *in = smp2p->in; + bool restart; + + if (!smp2p->ssr_ack_enabled) + return false; + + restart = in->flags & BIT(SMP2P_FLAGS_RESTART_DONE_BIT); + + return restart != smp2p->ssr_ack; +} + +static void qcom_smp2p_do_ssr_ack(struct qcom_smp2p *smp2p) +{ + struct smp2p_smem_item *out = smp2p->out; + u32 val; + + smp2p->ssr_ack = !smp2p->ssr_ack; + + val = out->flags & ~BIT(SMP2P_FLAGS_RESTART_ACK_BIT); + if (smp2p->ssr_ack) + val |= BIT(SMP2P_FLAGS_RESTART_ACK_BIT); + out->flags = val; + + qcom_smp2p_kick(smp2p); +} + +static void qcom_smp2p_negotiate(struct qcom_smp2p *smp2p) +{ + struct smp2p_smem_item *out = smp2p->out; + struct smp2p_smem_item *in = smp2p->in; + + if (in->version == out->version) { + out->features &= in->features; + + if (out->features & SMP2P_FEATURE_SSR_ACK) + smp2p->ssr_ack_enabled = true; + + smp2p->negotiation_done = true; + } +} + +static void qcom_smp2p_notify_in(struct qcom_smp2p *smp2p) { struct smp2p_smem_item *in; struct smp2p_entry *entry; - struct qcom_smp2p *smp2p = data; - unsigned smem_id = smp2p->smem_items[SMP2P_INBOUND]; - unsigned pid = smp2p->remote_pid; - size_t size; int irq_pin; u32 status; char buf[SMP2P_MAX_ENTRY_NAME]; @@ -186,18 +225,6 @@ static irqreturn_t qcom_smp2p_intr(int irq, void *data) in = smp2p->in; - /* Acquire smem item, if not already found */ - if (!in) { - in = qcom_smem_get(pid, smem_id, &size); - if (IS_ERR(in)) { - dev_err(smp2p->dev, - "Unable to acquire remote smp2p item\n"); - return IRQ_HANDLED; - } - - smp2p->in = in; - } - /* Match newly created entries */ for (i = smp2p->valid_entries; i < in->valid_entries; i++) { list_for_each_entry(entry, &smp2p->inbound, node) { @@ -236,7 +263,51 @@ static irqreturn_t qcom_smp2p_intr(int irq, void *data) } } } +} + +/** + * qcom_smp2p_intr() - interrupt handler for incoming notifications + * @irq: unused + * @data: smp2p driver context + * + * Handle notifications from the remote side to handle newly allocated entries + * or any changes to the state bits of existing entries. + */ +static irqreturn_t qcom_smp2p_intr(int irq, void *data) +{ + struct smp2p_smem_item *in; + struct qcom_smp2p *smp2p = data; + unsigned int smem_id = smp2p->smem_items[SMP2P_INBOUND]; + unsigned int pid = smp2p->remote_pid; + bool ack_restart; + size_t size; + + in = smp2p->in; + + /* Acquire smem item, if not already found */ + if (!in) { + in = qcom_smem_get(pid, smem_id, &size); + if (IS_ERR(in)) { + dev_err(smp2p->dev, + "Unable to acquire remote smp2p item\n"); + goto out; + } + + smp2p->in = in; + } + + if (!smp2p->negotiation_done) + qcom_smp2p_negotiate(smp2p); + + if (smp2p->negotiation_done) { + ack_restart = qcom_smp2p_check_ssr(smp2p); + qcom_smp2p_notify_in(smp2p); + + if (ack_restart) + qcom_smp2p_do_ssr_ack(smp2p); + } +out: return IRQ_HANDLED; } @@ -392,6 +463,7 @@ static int qcom_smp2p_alloc_outbound_item(struct qcom_smp2p *smp2p) out->remote_pid = smp2p->remote_pid; out->total_entries = SMP2P_MAX_ENTRY; out->valid_entries = 0; + out->features = SMP2P_ALL_FEATURES; /* * Make sure the rest of the header is written before we validate the @@ -501,6 +573,7 @@ static int qcom_smp2p_probe(struct platform_device *pdev) entry = devm_kzalloc(&pdev->dev, sizeof(*entry), GFP_KERNEL); if (!entry) { ret = -ENOMEM; + of_node_put(node); goto unwind_interfaces; } @@ -508,19 +581,25 @@ static int qcom_smp2p_probe(struct platform_device *pdev) spin_lock_init(&entry->lock); ret = of_property_read_string(node, "qcom,entry-name", &entry->name); - if (ret < 0) + if (ret < 0) { + of_node_put(node); goto unwind_interfaces; + } if (of_property_read_bool(node, "interrupt-controller")) { ret = qcom_smp2p_inbound_entry(smp2p, entry, node); - if (ret < 0) + if (ret < 0) { + of_node_put(node); goto unwind_interfaces; + } list_add(&entry->node, &smp2p->inbound); } else { ret = qcom_smp2p_outbound_entry(smp2p, entry, node); - if (ret < 0) + if (ret < 0) { + of_node_put(node); goto unwind_interfaces; + } list_add(&entry->node, &smp2p->outbound); } @@ -538,9 +617,26 @@ static int qcom_smp2p_probe(struct platform_device *pdev) goto unwind_interfaces; } + /* + * Treat smp2p interrupt as wakeup source, but keep it disabled + * by default. User space can decide enabling it depending on its + * use cases. For example if remoteproc crashes and device wants + * to handle it immediatedly (e.g. to not miss phone calls) it can + * enable wakeup source from user space, while other devices which + * do not have proper autosleep feature may want to handle it with + * other wakeup events (e.g. Power button) instead waking up immediately. + */ + device_set_wakeup_capable(&pdev->dev, true); + + ret = dev_pm_set_wake_irq(&pdev->dev, irq); + if (ret) + goto set_wake_irq_fail; return 0; +set_wake_irq_fail: + dev_pm_clear_wake_irq(&pdev->dev); + unwind_interfaces: list_for_each_entry(entry, &smp2p->inbound, node) irq_domain_remove(entry->domain); @@ -565,6 +661,8 @@ static int qcom_smp2p_remove(struct platform_device *pdev) struct qcom_smp2p *smp2p = platform_get_drvdata(pdev); struct smp2p_entry *entry; + dev_pm_clear_wake_irq(&pdev->dev); + list_for_each_entry(entry, &smp2p->inbound, node) irq_domain_remove(entry->domain); diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c index 52e581167115..9a0eb59405e8 100644 --- a/drivers/soc/qcom/socinfo.c +++ b/drivers/soc/qcom/socinfo.c @@ -87,8 +87,8 @@ static const char *const pmic_models[] = { [15] = "PM8901", [16] = "PM8950/PM8027", [17] = "PMI8950/ISL9519", - [18] = "PM8921", - [19] = "PM8018", + [18] = "PMK8001/PM8921", + [19] = "PMI8996/PM8018", [20] = "PM8998/PM8015", [21] = "PMI8998/PM8014", [22] = "PM8821", @@ -102,6 +102,8 @@ static const char *const pmic_models[] = { [32] = "PM8150B", [33] = "PMK8002", [36] = "PM8009", + [38] = "PM8150C", + [41] = "SMB2351", }; #endif /* CONFIG_DEBUG_FS */ @@ -281,19 +283,31 @@ static const struct soc_id soc_id[] = { { 319, "APQ8098" }, { 321, "SDM845" }, { 322, "MDM9206" }, + { 323, "IPQ8074" }, { 324, "SDA660" }, { 325, "SDM658" }, { 326, "SDA658" }, { 327, "SDA630" }, { 338, "SDM450" }, { 341, "SDA845" }, + { 342, "IPQ8072" }, + { 343, "IPQ8076" }, + { 344, "IPQ8078" }, { 345, "SDM636" }, { 346, "SDA636" }, { 349, "SDM632" }, { 350, "SDA632" }, { 351, "SDA450" }, { 356, "SM8250" }, + { 375, "IPQ8070" }, + { 376, "IPQ8071" }, + { 389, "IPQ8072A" }, + { 390, "IPQ8074A" }, + { 391, "IPQ8076A" }, + { 392, "IPQ8078A" }, { 394, "SM6125" }, + { 395, "IPQ8070A" }, + { 396, "IPQ8071A" }, { 402, "IPQ6018" }, { 403, "IPQ6028" }, { 421, "IPQ6000" }, diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c new file mode 100644 index 000000000000..f831420b7fd4 --- /dev/null +++ b/drivers/soc/qcom/spm.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2014,2015, Linaro Ltd. + * + * SAW power controller driver + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <soc/qcom/spm.h> + +#define SPM_CTL_INDEX 0x7f +#define SPM_CTL_INDEX_SHIFT 4 +#define SPM_CTL_EN BIT(0) + +enum spm_reg { + SPM_REG_CFG, + SPM_REG_SPM_CTL, + SPM_REG_DLY, + SPM_REG_PMIC_DLY, + SPM_REG_PMIC_DATA_0, + SPM_REG_PMIC_DATA_1, + SPM_REG_VCTL, + SPM_REG_SEQ_ENTRY, + SPM_REG_SPM_STS, + SPM_REG_PMIC_STS, + SPM_REG_AVS_CTL, + SPM_REG_AVS_LIMIT, + SPM_REG_NR, +}; + +static const u16 spm_reg_offset_v4_1[SPM_REG_NR] = { + [SPM_REG_AVS_CTL] = 0x904, + [SPM_REG_AVS_LIMIT] = 0x908, +}; + +static const struct spm_reg_data spm_reg_660_gold_l2 = { + .reg_offset = spm_reg_offset_v4_1, + .avs_ctl = 0x1010031, + .avs_limit = 0x4580458, +}; + +static const struct spm_reg_data spm_reg_660_silver_l2 = { + .reg_offset = spm_reg_offset_v4_1, + .avs_ctl = 0x101c031, + .avs_limit = 0x4580458, +}; + +static const struct spm_reg_data spm_reg_8998_gold_l2 = { + .reg_offset = spm_reg_offset_v4_1, + .avs_ctl = 0x1010031, + .avs_limit = 0x4700470, +}; + +static const struct spm_reg_data spm_reg_8998_silver_l2 = { + .reg_offset = spm_reg_offset_v4_1, + .avs_ctl = 0x1010031, + .avs_limit = 0x4200420, +}; + +static const u16 spm_reg_offset_v3_0[SPM_REG_NR] = { + [SPM_REG_CFG] = 0x08, + [SPM_REG_SPM_CTL] = 0x30, + [SPM_REG_DLY] = 0x34, + [SPM_REG_SEQ_ENTRY] = 0x400, +}; + +/* SPM register data for 8916 */ +static const struct spm_reg_data spm_reg_8916_cpu = { + .reg_offset = spm_reg_offset_v3_0, + .spm_cfg = 0x1, + .spm_dly = 0x3C102800, + .seq = { 0x60, 0x03, 0x60, 0x0B, 0x0F, 0x20, 0x10, 0x80, 0x30, 0x90, + 0x5B, 0x60, 0x03, 0x60, 0x3B, 0x76, 0x76, 0x0B, 0x94, 0x5B, + 0x80, 0x10, 0x26, 0x30, 0x0F }, + .start_index[PM_SLEEP_MODE_STBY] = 0, + .start_index[PM_SLEEP_MODE_SPC] = 5, +}; + +static const u16 spm_reg_offset_v2_1[SPM_REG_NR] = { + [SPM_REG_CFG] = 0x08, + [SPM_REG_SPM_CTL] = 0x30, + [SPM_REG_DLY] = 0x34, + [SPM_REG_SEQ_ENTRY] = 0x80, +}; + +/* SPM register data for 8974, 8084 */ +static const struct spm_reg_data spm_reg_8974_8084_cpu = { + .reg_offset = spm_reg_offset_v2_1, + .spm_cfg = 0x1, + .spm_dly = 0x3C102800, + .seq = { 0x03, 0x0B, 0x0F, 0x00, 0x20, 0x80, 0x10, 0xE8, 0x5B, 0x03, + 0x3B, 0xE8, 0x5B, 0x82, 0x10, 0x0B, 0x30, 0x06, 0x26, 0x30, + 0x0F }, + .start_index[PM_SLEEP_MODE_STBY] = 0, + .start_index[PM_SLEEP_MODE_SPC] = 3, +}; + +/* SPM register data for 8226 */ +static const struct spm_reg_data spm_reg_8226_cpu = { + .reg_offset = spm_reg_offset_v2_1, + .spm_cfg = 0x0, + .spm_dly = 0x3C102800, + .seq = { 0x60, 0x03, 0x60, 0x0B, 0x0F, 0x20, 0x10, 0x80, 0x30, 0x90, + 0x5B, 0x60, 0x03, 0x60, 0x3B, 0x76, 0x76, 0x0B, 0x94, 0x5B, + 0x80, 0x10, 0x26, 0x30, 0x0F }, + .start_index[PM_SLEEP_MODE_STBY] = 0, + .start_index[PM_SLEEP_MODE_SPC] = 5, +}; + +static const u16 spm_reg_offset_v1_1[SPM_REG_NR] = { + [SPM_REG_CFG] = 0x08, + [SPM_REG_SPM_CTL] = 0x20, + [SPM_REG_PMIC_DLY] = 0x24, + [SPM_REG_PMIC_DATA_0] = 0x28, + [SPM_REG_PMIC_DATA_1] = 0x2C, + [SPM_REG_SEQ_ENTRY] = 0x80, +}; + +/* SPM register data for 8064 */ +static const struct spm_reg_data spm_reg_8064_cpu = { + .reg_offset = spm_reg_offset_v1_1, + .spm_cfg = 0x1F, + .pmic_dly = 0x02020004, + .pmic_data[0] = 0x0084009C, + .pmic_data[1] = 0x00A4001C, + .seq = { 0x03, 0x0F, 0x00, 0x24, 0x54, 0x10, 0x09, 0x03, 0x01, + 0x10, 0x54, 0x30, 0x0C, 0x24, 0x30, 0x0F }, + .start_index[PM_SLEEP_MODE_STBY] = 0, + .start_index[PM_SLEEP_MODE_SPC] = 2, +}; + +static inline void spm_register_write(struct spm_driver_data *drv, + enum spm_reg reg, u32 val) +{ + if (drv->reg_data->reg_offset[reg]) + writel_relaxed(val, drv->reg_base + + drv->reg_data->reg_offset[reg]); +} + +/* Ensure a guaranteed write, before return */ +static inline void spm_register_write_sync(struct spm_driver_data *drv, + enum spm_reg reg, u32 val) +{ + u32 ret; + + if (!drv->reg_data->reg_offset[reg]) + return; + + do { + writel_relaxed(val, drv->reg_base + + drv->reg_data->reg_offset[reg]); + ret = readl_relaxed(drv->reg_base + + drv->reg_data->reg_offset[reg]); + if (ret == val) + break; + cpu_relax(); + } while (1); +} + +static inline u32 spm_register_read(struct spm_driver_data *drv, + enum spm_reg reg) +{ + return readl_relaxed(drv->reg_base + drv->reg_data->reg_offset[reg]); +} + +void spm_set_low_power_mode(struct spm_driver_data *drv, + enum pm_sleep_mode mode) +{ + u32 start_index; + u32 ctl_val; + + start_index = drv->reg_data->start_index[mode]; + + ctl_val = spm_register_read(drv, SPM_REG_SPM_CTL); + ctl_val &= ~(SPM_CTL_INDEX << SPM_CTL_INDEX_SHIFT); + ctl_val |= start_index << SPM_CTL_INDEX_SHIFT; + ctl_val |= SPM_CTL_EN; + spm_register_write_sync(drv, SPM_REG_SPM_CTL, ctl_val); +} + +static const struct of_device_id spm_match_table[] = { + { .compatible = "qcom,sdm660-gold-saw2-v4.1-l2", + .data = &spm_reg_660_gold_l2 }, + { .compatible = "qcom,sdm660-silver-saw2-v4.1-l2", + .data = &spm_reg_660_silver_l2 }, + { .compatible = "qcom,msm8226-saw2-v2.1-cpu", + .data = &spm_reg_8226_cpu }, + { .compatible = "qcom,msm8916-saw2-v3.0-cpu", + .data = &spm_reg_8916_cpu }, + { .compatible = "qcom,msm8974-saw2-v2.1-cpu", + .data = &spm_reg_8974_8084_cpu }, + { .compatible = "qcom,msm8998-gold-saw2-v4.1-l2", + .data = &spm_reg_8998_gold_l2 }, + { .compatible = "qcom,msm8998-silver-saw2-v4.1-l2", + .data = &spm_reg_8998_silver_l2 }, + { .compatible = "qcom,apq8084-saw2-v2.1-cpu", + .data = &spm_reg_8974_8084_cpu }, + { .compatible = "qcom,apq8064-saw2-v1.1-cpu", + .data = &spm_reg_8064_cpu }, + { }, +}; +MODULE_DEVICE_TABLE(of, spm_match_table); + +static int spm_dev_probe(struct platform_device *pdev) +{ + const struct of_device_id *match_id; + struct spm_driver_data *drv; + struct resource *res; + void __iomem *addr; + + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drv->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(drv->reg_base)) + return PTR_ERR(drv->reg_base); + + match_id = of_match_node(spm_match_table, pdev->dev.of_node); + if (!match_id) + return -ENODEV; + + drv->reg_data = match_id->data; + platform_set_drvdata(pdev, drv); + + /* Write the SPM sequences first.. */ + addr = drv->reg_base + drv->reg_data->reg_offset[SPM_REG_SEQ_ENTRY]; + __iowrite32_copy(addr, drv->reg_data->seq, + ARRAY_SIZE(drv->reg_data->seq) / 4); + + /* + * ..and then the control registers. + * On some SoC if the control registers are written first and if the + * CPU was held in reset, the reset signal could trigger the SPM state + * machine, before the sequences are completely written. + */ + spm_register_write(drv, SPM_REG_AVS_CTL, drv->reg_data->avs_ctl); + spm_register_write(drv, SPM_REG_AVS_LIMIT, drv->reg_data->avs_limit); + spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg); + spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly); + spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly); + spm_register_write(drv, SPM_REG_PMIC_DATA_0, + drv->reg_data->pmic_data[0]); + spm_register_write(drv, SPM_REG_PMIC_DATA_1, + drv->reg_data->pmic_data[1]); + + /* Set up Standby as the default low power mode */ + if (drv->reg_data->reg_offset[SPM_REG_SPM_CTL]) + spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY); + + return 0; +} + +static struct platform_driver spm_driver = { + .probe = spm_dev_probe, + .driver = { + .name = "qcom_spm", + .of_match_table = spm_match_table, + }, +}; + +static int __init qcom_spm_init(void) +{ + return platform_driver_register(&spm_driver); +} +arch_initcall(qcom_spm_init); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig index 07e0ecd64319..ce16ef5c939c 100644 --- a/drivers/soc/renesas/Kconfig +++ b/drivers/soc/renesas/Kconfig @@ -186,6 +186,7 @@ config ARCH_R8A77995 select SYSC_R8A77995 help This enables support for the Renesas R-Car D3 SoC. + This includes different gradings like R-Car D3e. config ARCH_R8A77990 bool "ARM64 Platform support for R-Car E3" @@ -193,6 +194,7 @@ config ARCH_R8A77990 select SYSC_R8A77990 help This enables support for the Renesas R-Car E3 SoC. + This includes different gradings like R-Car E3e. config ARCH_R8A77950 bool "ARM64 Platform support for R-Car H3 ES1.x" @@ -208,7 +210,7 @@ config ARCH_R8A77951 help This enables support for the Renesas R-Car H3 SoC (revisions 2.0 and later). - This includes different gradings like R-Car H3e-2G. + This includes different gradings like R-Car H3e, H3e-2G, and H3Ne. config ARCH_R8A77965 bool "ARM64 Platform support for R-Car M3-N" @@ -216,6 +218,7 @@ config ARCH_R8A77965 select SYSC_R8A77965 help This enables support for the Renesas R-Car M3-N SoC. + This includes different gradings like R-Car M3Ne and M3Ne-2G. config ARCH_R8A77960 bool "ARM64 Platform support for R-Car M3-W" @@ -230,7 +233,7 @@ config ARCH_R8A77961 select SYSC_R8A77961 help This enables support for the Renesas R-Car M3-W+ SoC. - This includes different gradings like R-Car M3e-2G. + This includes different gradings like R-Car M3e and M3e-2G. config ARCH_R8A77980 bool "ARM64 Platform support for R-Car V3H" diff --git a/drivers/soc/renesas/renesas-soc.c b/drivers/soc/renesas/renesas-soc.c index dab9f5a0aad0..7961b0be1850 100644 --- a/drivers/soc/renesas/renesas-soc.c +++ b/drivers/soc/renesas/renesas-soc.c @@ -285,17 +285,22 @@ static const struct of_device_id renesas_socs[] __initconst = { { .compatible = "renesas,r8a7795", .data = &soc_rcar_h3 }, #endif #ifdef CONFIG_ARCH_R8A77951 + { .compatible = "renesas,r8a779m0", .data = &soc_rcar_h3 }, { .compatible = "renesas,r8a779m1", .data = &soc_rcar_h3 }, + { .compatible = "renesas,r8a779m8", .data = &soc_rcar_h3 }, #endif #ifdef CONFIG_ARCH_R8A77960 { .compatible = "renesas,r8a7796", .data = &soc_rcar_m3_w }, #endif #ifdef CONFIG_ARCH_R8A77961 { .compatible = "renesas,r8a77961", .data = &soc_rcar_m3_w }, + { .compatible = "renesas,r8a779m2", .data = &soc_rcar_m3_w }, { .compatible = "renesas,r8a779m3", .data = &soc_rcar_m3_w }, #endif #ifdef CONFIG_ARCH_R8A77965 { .compatible = "renesas,r8a77965", .data = &soc_rcar_m3_n }, + { .compatible = "renesas,r8a779m4", .data = &soc_rcar_m3_n }, + { .compatible = "renesas,r8a779m5", .data = &soc_rcar_m3_n }, #endif #ifdef CONFIG_ARCH_R8A77970 { .compatible = "renesas,r8a77970", .data = &soc_rcar_v3m }, @@ -305,9 +310,11 @@ static const struct of_device_id renesas_socs[] __initconst = { #endif #ifdef CONFIG_ARCH_R8A77990 { .compatible = "renesas,r8a77990", .data = &soc_rcar_e3 }, + { .compatible = "renesas,r8a779m6", .data = &soc_rcar_e3 }, #endif #ifdef CONFIG_ARCH_R8A77995 { .compatible = "renesas,r8a77995", .data = &soc_rcar_d3 }, + { .compatible = "renesas,r8a779m7", .data = &soc_rcar_d3 }, #endif #ifdef CONFIG_ARCH_R8A779A0 { .compatible = "renesas,r8a779a0", .data = &soc_rcar_v3u }, diff --git a/drivers/soc/samsung/Kconfig b/drivers/soc/samsung/Kconfig index 5745d7e5908e..e2cedef1e8d1 100644 --- a/drivers/soc/samsung/Kconfig +++ b/drivers/soc/samsung/Kconfig @@ -13,18 +13,21 @@ config EXYNOS_ASV_ARM depends on EXYNOS_CHIPID config EXYNOS_CHIPID - bool "Exynos ChipID controller and ASV driver" if COMPILE_TEST + tristate "Exynos ChipID controller and ASV driver" depends on ARCH_EXYNOS || COMPILE_TEST + default ARCH_EXYNOS select EXYNOS_ASV_ARM if ARM && ARCH_EXYNOS select MFD_SYSCON select SOC_BUS help Support for Samsung Exynos SoC ChipID and Adaptive Supply Voltage. + This driver can also be built as module (exynos_chipid). config EXYNOS_PMU bool "Exynos PMU controller driver" if COMPILE_TEST depends on ARCH_EXYNOS || ((ARM || ARM64) && COMPILE_TEST) select EXYNOS_PMU_ARM_DRIVERS if ARM && ARCH_EXYNOS + select MFD_CORE # There is no need to enable these drivers for ARMv8 config EXYNOS_PMU_ARM_DRIVERS diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile index 0c523a8de4eb..2ae4bea804cf 100644 --- a/drivers/soc/samsung/Makefile +++ b/drivers/soc/samsung/Makefile @@ -1,8 +1,9 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_EXYNOS_ASV_ARM) += exynos5422-asv.o +obj-$(CONFIG_EXYNOS_CHIPID) += exynos_chipid.o +exynos_chipid-y += exynos-chipid.o exynos-asv.o -obj-$(CONFIG_EXYNOS_CHIPID) += exynos-chipid.o exynos-asv.o obj-$(CONFIG_EXYNOS_PMU) += exynos-pmu.o obj-$(CONFIG_EXYNOS_PMU_ARM_DRIVERS) += exynos3250-pmu.o exynos4-pmu.o \ diff --git a/drivers/soc/samsung/exynos-chipid.c b/drivers/soc/samsung/exynos-chipid.c index 5c1d0f97f766..a28053ec7e6a 100644 --- a/drivers/soc/samsung/exynos-chipid.c +++ b/drivers/soc/samsung/exynos-chipid.c @@ -15,7 +15,9 @@ #include <linux/device.h> #include <linux/errno.h> #include <linux/mfd/syscon.h> +#include <linux/module.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/slab.h> @@ -24,6 +26,17 @@ #include "exynos-asv.h" +struct exynos_chipid_variant { + unsigned int rev_reg; /* revision register offset */ + unsigned int main_rev_shift; /* main revision offset in rev_reg */ + unsigned int sub_rev_shift; /* sub revision offset in rev_reg */ +}; + +struct exynos_chipid_info { + u32 product_id; + u32 revision; +}; + static const struct exynos_soc_id { const char *name; unsigned int id; @@ -42,6 +55,8 @@ static const struct exynos_soc_id { { "EXYNOS5440", 0xE5440000 }, { "EXYNOS5800", 0xE5422000 }, { "EXYNOS7420", 0xE7420000 }, + { "EXYNOS850", 0xE3830000 }, + { "EXYNOSAUTOV9", 0xAAA80000 }, }; static const char *product_id_to_soc_id(unsigned int product_id) @@ -49,31 +64,57 @@ static const char *product_id_to_soc_id(unsigned int product_id) int i; for (i = 0; i < ARRAY_SIZE(soc_ids); i++) - if ((product_id & EXYNOS_MASK) == soc_ids[i].id) + if (product_id == soc_ids[i].id) return soc_ids[i].name; return NULL; } +static int exynos_chipid_get_chipid_info(struct regmap *regmap, + const struct exynos_chipid_variant *data, + struct exynos_chipid_info *soc_info) +{ + int ret; + unsigned int val, main_rev, sub_rev; + + ret = regmap_read(regmap, EXYNOS_CHIPID_REG_PRO_ID, &val); + if (ret < 0) + return ret; + soc_info->product_id = val & EXYNOS_MASK; + + if (data->rev_reg != EXYNOS_CHIPID_REG_PRO_ID) { + ret = regmap_read(regmap, data->rev_reg, &val); + if (ret < 0) + return ret; + } + main_rev = (val >> data->main_rev_shift) & EXYNOS_REV_PART_MASK; + sub_rev = (val >> data->sub_rev_shift) & EXYNOS_REV_PART_MASK; + soc_info->revision = (main_rev << EXYNOS_REV_PART_SHIFT) | sub_rev; + + return 0; +} + static int exynos_chipid_probe(struct platform_device *pdev) { + const struct exynos_chipid_variant *drv_data; + struct exynos_chipid_info soc_info; struct soc_device_attribute *soc_dev_attr; struct soc_device *soc_dev; struct device_node *root; struct regmap *regmap; - u32 product_id; - u32 revision; int ret; + drv_data = of_device_get_match_data(&pdev->dev); + if (!drv_data) + return -EINVAL; + regmap = device_node_to_regmap(pdev->dev.of_node); if (IS_ERR(regmap)) return PTR_ERR(regmap); - ret = regmap_read(regmap, EXYNOS_CHIPID_REG_PRO_ID, &product_id); + ret = exynos_chipid_get_chipid_info(regmap, drv_data, &soc_info); if (ret < 0) return ret; - revision = product_id & EXYNOS_REV_MASK; - soc_dev_attr = devm_kzalloc(&pdev->dev, sizeof(*soc_dev_attr), GFP_KERNEL); if (!soc_dev_attr) @@ -86,8 +127,8 @@ static int exynos_chipid_probe(struct platform_device *pdev) of_node_put(root); soc_dev_attr->revision = devm_kasprintf(&pdev->dev, GFP_KERNEL, - "%x", revision); - soc_dev_attr->soc_id = product_id_to_soc_id(product_id); + "%x", soc_info.revision); + soc_dev_attr->soc_id = product_id_to_soc_id(soc_info.product_id); if (!soc_dev_attr->soc_id) { pr_err("Unknown SoC\n"); return -ENODEV; @@ -104,9 +145,8 @@ static int exynos_chipid_probe(struct platform_device *pdev) platform_set_drvdata(pdev, soc_dev); - dev_info(soc_device_to_device(soc_dev), - "Exynos: CPU[%s] PRO_ID[0x%x] REV[0x%x] Detected\n", - soc_dev_attr->soc_id, product_id, revision); + dev_info(&pdev->dev, "Exynos: CPU[%s] PRO_ID[0x%x] REV[0x%x] Detected\n", + soc_dev_attr->soc_id, soc_info.product_id, soc_info.revision); return 0; @@ -125,10 +165,29 @@ static int exynos_chipid_remove(struct platform_device *pdev) return 0; } +static const struct exynos_chipid_variant exynos4210_chipid_drv_data = { + .rev_reg = 0x0, + .main_rev_shift = 4, + .sub_rev_shift = 0, +}; + +static const struct exynos_chipid_variant exynos850_chipid_drv_data = { + .rev_reg = 0x10, + .main_rev_shift = 20, + .sub_rev_shift = 16, +}; + static const struct of_device_id exynos_chipid_of_device_ids[] = { - { .compatible = "samsung,exynos4210-chipid" }, - {} + { + .compatible = "samsung,exynos4210-chipid", + .data = &exynos4210_chipid_drv_data, + }, { + .compatible = "samsung,exynos850-chipid", + .data = &exynos850_chipid_drv_data, + }, + { } }; +MODULE_DEVICE_TABLE(of, exynos_chipid_of_device_ids); static struct platform_driver exynos_chipid_driver = { .driver = { @@ -138,4 +197,11 @@ static struct platform_driver exynos_chipid_driver = { .probe = exynos_chipid_probe, .remove = exynos_chipid_remove, }; -builtin_platform_driver(exynos_chipid_driver); +module_platform_driver(exynos_chipid_driver); + +MODULE_DESCRIPTION("Samsung Exynos ChipID controller and ASV driver"); +MODULE_AUTHOR("Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>"); +MODULE_AUTHOR("Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com>"); +MODULE_AUTHOR("Pankaj Dubey <pankaj.dubey@samsung.com>"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/samsung/exynos5422-asv.c b/drivers/soc/samsung/exynos5422-asv.c index ca409a976e34..475ae5276529 100644 --- a/drivers/soc/samsung/exynos5422-asv.c +++ b/drivers/soc/samsung/exynos5422-asv.c @@ -503,3 +503,4 @@ int exynos5422_asv_init(struct exynos_asv *asv) return 0; } +EXPORT_SYMBOL_GPL(exynos5422_asv_init); diff --git a/drivers/soc/samsung/pm_domains.c b/drivers/soc/samsung/pm_domains.c index 5ec0c13f0aaf..d07f3c9d6903 100644 --- a/drivers/soc/samsung/pm_domains.c +++ b/drivers/soc/samsung/pm_domains.c @@ -28,7 +28,6 @@ struct exynos_pm_domain_config { */ struct exynos_pm_domain { void __iomem *base; - bool is_off; struct generic_pm_domain pd; u32 local_pwr_cfg; }; diff --git a/drivers/soc/sunxi/sunxi_sram.c b/drivers/soc/sunxi/sunxi_sram.c index 42833e33a96c..a8f3876963a0 100644 --- a/drivers/soc/sunxi/sunxi_sram.c +++ b/drivers/soc/sunxi/sunxi_sram.c @@ -331,7 +331,6 @@ static struct regmap_config sunxi_sram_emac_clock_regmap = { static int sunxi_sram_probe(struct platform_device *pdev) { - struct resource *res; struct dentry *d; struct regmap *emac_clock; const struct sunxi_sramc_variant *variant; @@ -342,8 +341,7 @@ static int sunxi_sram_probe(struct platform_device *pdev) if (!variant) return -EINVAL; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile index 9c809c1814bd..054e862b63d8 100644 --- a/drivers/soc/tegra/Makefile +++ b/drivers/soc/tegra/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o obj-$(CONFIG_SOC_TEGRA_POWERGATE_BPMP) += powergate-bpmp.o obj-$(CONFIG_SOC_TEGRA20_VOLTAGE_COUPLER) += regulators-tegra20.o obj-$(CONFIG_SOC_TEGRA30_VOLTAGE_COUPLER) += regulators-tegra30.o +obj-$(CONFIG_ARCH_TEGRA_186_SOC) += ari-tegra186.o diff --git a/drivers/soc/tegra/ari-tegra186.c b/drivers/soc/tegra/ari-tegra186.c new file mode 100644 index 000000000000..02577853ec49 --- /dev/null +++ b/drivers/soc/tegra/ari-tegra186.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. + */ + +#include <linux/arm-smccc.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/panic_notifier.h> + +#define SMC_SIP_INVOKE_MCE 0xc2ffff00 +#define MCE_SMC_READ_MCA 12 + +#define MCA_ARI_CMD_RD_SERR 1 + +#define MCA_ARI_RW_SUBIDX_STAT 1 +#define SERR_STATUS_VAL BIT_ULL(63) + +#define MCA_ARI_RW_SUBIDX_ADDR 2 +#define MCA_ARI_RW_SUBIDX_MSC1 3 +#define MCA_ARI_RW_SUBIDX_MSC2 4 + +static const char * const bank_names[] = { + "SYS:DPMU", "ROC:IOB", "ROC:MCB", "ROC:CCE", "ROC:CQX", "ROC:CTU", +}; + +static void read_uncore_mca(u8 cmd, u8 idx, u8 subidx, u8 inst, u64 *data) +{ + struct arm_smccc_res res; + + arm_smccc_smc(SMC_SIP_INVOKE_MCE | MCE_SMC_READ_MCA, + ((u64)inst << 24) | ((u64)idx << 16) | + ((u64)subidx << 8) | ((u64)cmd << 0), + 0, 0, 0, 0, 0, 0, &res); + + *data = res.a2; +} + +static int tegra186_ari_panic_handler(struct notifier_block *nb, + unsigned long code, void *unused) +{ + u64 status; + int i; + + for (i = 0; i < ARRAY_SIZE(bank_names); i++) { + read_uncore_mca(MCA_ARI_CMD_RD_SERR, i, MCA_ARI_RW_SUBIDX_STAT, + 0, &status); + + if (status & SERR_STATUS_VAL) { + u64 addr, misc1, misc2; + + read_uncore_mca(MCA_ARI_CMD_RD_SERR, i, + MCA_ARI_RW_SUBIDX_ADDR, 0, &addr); + read_uncore_mca(MCA_ARI_CMD_RD_SERR, i, + MCA_ARI_RW_SUBIDX_MSC1, 0, &misc1); + read_uncore_mca(MCA_ARI_CMD_RD_SERR, i, + MCA_ARI_RW_SUBIDX_MSC2, 0, &misc2); + + pr_crit("Machine Check Error in %s\n" + " status=0x%llx addr=0x%llx\n" + " msc1=0x%llx msc2=0x%llx\n", + bank_names[i], status, addr, misc1, misc2); + } + } + + return NOTIFY_DONE; +} + +static struct notifier_block tegra186_ari_panic_nb = { + .notifier_call = tegra186_ari_panic_handler, +}; + +static int __init tegra186_ari_init(void) +{ + if (of_machine_is_compatible("nvidia,tegra186")) + atomic_notifier_chain_register(&panic_notifier_list, &tegra186_ari_panic_nb); + + return 0; +} +early_initcall(tegra186_ari_init); diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 50091c4ec948..575d6d5b4294 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -360,6 +360,7 @@ struct tegra_pmc_soc { unsigned int num_pmc_clks; bool has_blink_output; bool has_usb_sleepwalk; + bool supports_core_domain; }; /** @@ -782,7 +783,7 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg, err = reset_control_deassert(pg->reset); if (err) - goto powergate_off; + goto disable_clks; usleep_range(10, 20); @@ -2815,8 +2816,7 @@ static int tegra_pmc_probe(struct platform_device *pdev) return err; /* take over the memory region from the early initialization */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); @@ -3041,6 +3041,7 @@ static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc, } static const struct tegra_pmc_soc tegra20_pmc_soc = { + .supports_core_domain = false, .num_powergates = ARRAY_SIZE(tegra20_powergates), .powergates = tegra20_powergates, .num_cpu_powergates = 0, @@ -3065,7 +3066,7 @@ static const struct tegra_pmc_soc tegra20_pmc_soc = { .pmc_clks_data = NULL, .num_pmc_clks = 0, .has_blink_output = true, - .has_usb_sleepwalk = false, + .has_usb_sleepwalk = true, }; static const char * const tegra30_powergates[] = { @@ -3101,6 +3102,7 @@ static const char * const tegra30_reset_sources[] = { }; static const struct tegra_pmc_soc tegra30_pmc_soc = { + .supports_core_domain = false, .num_powergates = ARRAY_SIZE(tegra30_powergates), .powergates = tegra30_powergates, .num_cpu_powergates = ARRAY_SIZE(tegra30_cpu_powergates), @@ -3125,7 +3127,7 @@ static const struct tegra_pmc_soc tegra30_pmc_soc = { .pmc_clks_data = tegra_pmc_clks_data, .num_pmc_clks = ARRAY_SIZE(tegra_pmc_clks_data), .has_blink_output = true, - .has_usb_sleepwalk = false, + .has_usb_sleepwalk = true, }; static const char * const tegra114_powergates[] = { @@ -3157,6 +3159,7 @@ static const u8 tegra114_cpu_powergates[] = { }; static const struct tegra_pmc_soc tegra114_pmc_soc = { + .supports_core_domain = false, .num_powergates = ARRAY_SIZE(tegra114_powergates), .powergates = tegra114_powergates, .num_cpu_powergates = ARRAY_SIZE(tegra114_cpu_powergates), @@ -3181,7 +3184,7 @@ static const struct tegra_pmc_soc tegra114_pmc_soc = { .pmc_clks_data = tegra_pmc_clks_data, .num_pmc_clks = ARRAY_SIZE(tegra_pmc_clks_data), .has_blink_output = true, - .has_usb_sleepwalk = false, + .has_usb_sleepwalk = true, }; static const char * const tegra124_powergates[] = { @@ -3273,6 +3276,7 @@ static const struct pinctrl_pin_desc tegra124_pin_descs[] = { }; static const struct tegra_pmc_soc tegra124_pmc_soc = { + .supports_core_domain = false, .num_powergates = ARRAY_SIZE(tegra124_powergates), .powergates = tegra124_powergates, .num_cpu_powergates = ARRAY_SIZE(tegra124_cpu_powergates), @@ -3398,6 +3402,7 @@ static const struct tegra_wake_event tegra210_wake_events[] = { }; static const struct tegra_pmc_soc tegra210_pmc_soc = { + .supports_core_domain = false, .num_powergates = ARRAY_SIZE(tegra210_powergates), .powergates = tegra210_powergates, .num_cpu_powergates = ARRAY_SIZE(tegra210_cpu_powergates), @@ -3555,6 +3560,7 @@ static const struct tegra_wake_event tegra186_wake_events[] = { }; static const struct tegra_pmc_soc tegra186_pmc_soc = { + .supports_core_domain = false, .num_powergates = 0, .powergates = NULL, .num_cpu_powergates = 0, @@ -3689,6 +3695,7 @@ static const struct tegra_wake_event tegra194_wake_events[] = { }; static const struct tegra_pmc_soc tegra194_pmc_soc = { + .supports_core_domain = false, .num_powergates = 0, .powergates = NULL, .num_cpu_powergates = 0, @@ -3757,6 +3764,7 @@ static const char * const tegra234_reset_sources[] = { }; static const struct tegra_pmc_soc tegra234_pmc_soc = { + .supports_core_domain = false, .num_powergates = 0, .powergates = NULL, .num_cpu_powergates = 0, @@ -3804,6 +3812,14 @@ static void tegra_pmc_sync_state(struct device *dev) int err; /* + * Newer device-trees have power domains, but we need to prepare all + * device drivers with runtime PM and OPP support first, otherwise + * state syncing is unsafe. + */ + if (!pmc->soc->supports_core_domain) + return; + + /* * Older device-trees don't have core PD, and thus, there are * no dependencies that will block the state syncing. We shouldn't * mark the domain as synced in this case. diff --git a/drivers/tee/optee/Makefile b/drivers/tee/optee/Makefile index 3aa33ea9e6a6..66b8a17f14c4 100644 --- a/drivers/tee/optee/Makefile +++ b/drivers/tee/optee/Makefile @@ -4,8 +4,9 @@ optee-objs += core.o optee-objs += call.o optee-objs += rpc.o optee-objs += supp.o -optee-objs += shm_pool.o optee-objs += device.o +optee-objs += smc_abi.o +optee-objs += ffa_abi.o # for tracing framework to find optee_trace.h -CFLAGS_call.o := -I$(src) +CFLAGS_smc_abi.o := -I$(src) diff --git a/drivers/tee/optee/call.c b/drivers/tee/optee/call.c index 945f03da0223..b25cc1fac945 100644 --- a/drivers/tee/optee/call.c +++ b/drivers/tee/optee/call.c @@ -1,29 +1,18 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2015-2021, Linaro Limited */ -#include <linux/arm-smccc.h> #include <linux/device.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/mm.h> -#include <linux/sched.h> #include <linux/slab.h> #include <linux/tee_drv.h> #include <linux/types.h> -#include <linux/uaccess.h> #include "optee_private.h" -#include "optee_smc.h" -#define CREATE_TRACE_POINTS -#include "optee_trace.h" -struct optee_call_waiter { - struct list_head list_node; - struct completion c; -}; - -static void optee_cq_wait_init(struct optee_call_queue *cq, - struct optee_call_waiter *w) +void optee_cq_wait_init(struct optee_call_queue *cq, + struct optee_call_waiter *w) { /* * We're preparing to make a call to secure world. In case we can't @@ -47,8 +36,8 @@ static void optee_cq_wait_init(struct optee_call_queue *cq, mutex_unlock(&cq->mutex); } -static void optee_cq_wait_for_completion(struct optee_call_queue *cq, - struct optee_call_waiter *w) +void optee_cq_wait_for_completion(struct optee_call_queue *cq, + struct optee_call_waiter *w) { wait_for_completion(&w->c); @@ -74,8 +63,8 @@ static void optee_cq_complete_one(struct optee_call_queue *cq) } } -static void optee_cq_wait_final(struct optee_call_queue *cq, - struct optee_call_waiter *w) +void optee_cq_wait_final(struct optee_call_queue *cq, + struct optee_call_waiter *w) { /* * We're done with the call to secure world. The thread in secure @@ -115,97 +104,35 @@ static struct optee_session *find_session(struct optee_context_data *ctxdata, return NULL; } -/** - * optee_do_call_with_arg() - Do an SMC to OP-TEE in secure world - * @ctx: calling context - * @parg: physical address of message to pass to secure world - * - * Does and SMC to OP-TEE in secure world and handles eventual resulting - * Remote Procedure Calls (RPC) from OP-TEE. - * - * Returns return code from secure world, 0 is OK - */ -u32 optee_do_call_with_arg(struct tee_context *ctx, phys_addr_t parg) +struct tee_shm *optee_get_msg_arg(struct tee_context *ctx, size_t num_params, + struct optee_msg_arg **msg_arg) { struct optee *optee = tee_get_drvdata(ctx->teedev); - struct optee_call_waiter w; - struct optee_rpc_param param = { }; - struct optee_call_ctx call_ctx = { }; - u32 ret; - - param.a0 = OPTEE_SMC_CALL_WITH_ARG; - reg_pair_from_64(¶m.a1, ¶m.a2, parg); - /* Initialize waiter */ - optee_cq_wait_init(&optee->call_queue, &w); - while (true) { - struct arm_smccc_res res; - - trace_optee_invoke_fn_begin(¶m); - optee->invoke_fn(param.a0, param.a1, param.a2, param.a3, - param.a4, param.a5, param.a6, param.a7, - &res); - trace_optee_invoke_fn_end(¶m, &res); - - if (res.a0 == OPTEE_SMC_RETURN_ETHREAD_LIMIT) { - /* - * Out of threads in secure world, wait for a thread - * become available. - */ - optee_cq_wait_for_completion(&optee->call_queue, &w); - } else if (OPTEE_SMC_RETURN_IS_RPC(res.a0)) { - cond_resched(); - param.a0 = res.a0; - param.a1 = res.a1; - param.a2 = res.a2; - param.a3 = res.a3; - optee_handle_rpc(ctx, ¶m, &call_ctx); - } else { - ret = res.a0; - break; - } - } + size_t sz = OPTEE_MSG_GET_ARG_SIZE(num_params); + struct tee_shm *shm; + struct optee_msg_arg *ma; - optee_rpc_finalize_call(&call_ctx); /* - * We're done with our thread in secure world, if there's any - * thread waiters wake up one. + * rpc_arg_count is set to the number of allocated parameters in + * the RPC argument struct if a second MSG arg struct is expected. + * The second arg struct will then be used for RPC. */ - optee_cq_wait_final(&optee->call_queue, &w); - - return ret; -} + if (optee->rpc_arg_count) + sz += OPTEE_MSG_GET_ARG_SIZE(optee->rpc_arg_count); -static struct tee_shm *get_msg_arg(struct tee_context *ctx, size_t num_params, - struct optee_msg_arg **msg_arg, - phys_addr_t *msg_parg) -{ - int rc; - struct tee_shm *shm; - struct optee_msg_arg *ma; - - shm = tee_shm_alloc(ctx, OPTEE_MSG_GET_ARG_SIZE(num_params), - TEE_SHM_MAPPED | TEE_SHM_PRIV); + shm = tee_shm_alloc(ctx, sz, TEE_SHM_MAPPED | TEE_SHM_PRIV); if (IS_ERR(shm)) return shm; ma = tee_shm_get_va(shm, 0); if (IS_ERR(ma)) { - rc = PTR_ERR(ma); - goto out; + tee_shm_free(shm); + return (void *)ma; } - rc = tee_shm_get_pa(shm, 0, msg_parg); - if (rc) - goto out; - memset(ma, 0, OPTEE_MSG_GET_ARG_SIZE(num_params)); ma->num_params = num_params; *msg_arg = ma; -out: - if (rc) { - tee_shm_free(shm); - return ERR_PTR(rc); - } return shm; } @@ -214,16 +141,16 @@ int optee_open_session(struct tee_context *ctx, struct tee_ioctl_open_session_arg *arg, struct tee_param *param) { + struct optee *optee = tee_get_drvdata(ctx->teedev); struct optee_context_data *ctxdata = ctx->data; int rc; struct tee_shm *shm; struct optee_msg_arg *msg_arg; - phys_addr_t msg_parg; struct optee_session *sess = NULL; uuid_t client_uuid; /* +2 for the meta parameters added below */ - shm = get_msg_arg(ctx, arg->num_params + 2, &msg_arg, &msg_parg); + shm = optee_get_msg_arg(ctx, arg->num_params + 2, &msg_arg); if (IS_ERR(shm)) return PTR_ERR(shm); @@ -247,7 +174,8 @@ int optee_open_session(struct tee_context *ctx, goto out; export_uuid(msg_arg->params[1].u.octets, &client_uuid); - rc = optee_to_msg_param(msg_arg->params + 2, arg->num_params, param); + rc = optee->ops->to_msg_param(optee, msg_arg->params + 2, + arg->num_params, param); if (rc) goto out; @@ -257,7 +185,7 @@ int optee_open_session(struct tee_context *ctx, goto out; } - if (optee_do_call_with_arg(ctx, msg_parg)) { + if (optee->ops->do_call_with_arg(ctx, shm)) { msg_arg->ret = TEEC_ERROR_COMMUNICATION; msg_arg->ret_origin = TEEC_ORIGIN_COMMS; } @@ -272,7 +200,8 @@ int optee_open_session(struct tee_context *ctx, kfree(sess); } - if (optee_from_msg_param(param, arg->num_params, msg_arg->params + 2)) { + if (optee->ops->from_msg_param(optee, param, arg->num_params, + msg_arg->params + 2)) { arg->ret = TEEC_ERROR_COMMUNICATION; arg->ret_origin = TEEC_ORIGIN_COMMS; /* Close session again to avoid leakage */ @@ -288,12 +217,28 @@ out: return rc; } -int optee_close_session(struct tee_context *ctx, u32 session) +int optee_close_session_helper(struct tee_context *ctx, u32 session) { - struct optee_context_data *ctxdata = ctx->data; struct tee_shm *shm; + struct optee *optee = tee_get_drvdata(ctx->teedev); struct optee_msg_arg *msg_arg; - phys_addr_t msg_parg; + + shm = optee_get_msg_arg(ctx, 0, &msg_arg); + if (IS_ERR(shm)) + return PTR_ERR(shm); + + msg_arg->cmd = OPTEE_MSG_CMD_CLOSE_SESSION; + msg_arg->session = session; + optee->ops->do_call_with_arg(ctx, shm); + + tee_shm_free(shm); + + return 0; +} + +int optee_close_session(struct tee_context *ctx, u32 session) +{ + struct optee_context_data *ctxdata = ctx->data; struct optee_session *sess; /* Check that the session is valid and remove it from the list */ @@ -306,25 +251,16 @@ int optee_close_session(struct tee_context *ctx, u32 session) return -EINVAL; kfree(sess); - shm = get_msg_arg(ctx, 0, &msg_arg, &msg_parg); - if (IS_ERR(shm)) - return PTR_ERR(shm); - - msg_arg->cmd = OPTEE_MSG_CMD_CLOSE_SESSION; - msg_arg->session = session; - optee_do_call_with_arg(ctx, msg_parg); - - tee_shm_free(shm); - return 0; + return optee_close_session_helper(ctx, session); } int optee_invoke_func(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg, struct tee_param *param) { + struct optee *optee = tee_get_drvdata(ctx->teedev); struct optee_context_data *ctxdata = ctx->data; struct tee_shm *shm; struct optee_msg_arg *msg_arg; - phys_addr_t msg_parg; struct optee_session *sess; int rc; @@ -335,7 +271,7 @@ int optee_invoke_func(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg, if (!sess) return -EINVAL; - shm = get_msg_arg(ctx, arg->num_params, &msg_arg, &msg_parg); + shm = optee_get_msg_arg(ctx, arg->num_params, &msg_arg); if (IS_ERR(shm)) return PTR_ERR(shm); msg_arg->cmd = OPTEE_MSG_CMD_INVOKE_COMMAND; @@ -343,16 +279,18 @@ int optee_invoke_func(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg, msg_arg->session = arg->session; msg_arg->cancel_id = arg->cancel_id; - rc = optee_to_msg_param(msg_arg->params, arg->num_params, param); + rc = optee->ops->to_msg_param(optee, msg_arg->params, arg->num_params, + param); if (rc) goto out; - if (optee_do_call_with_arg(ctx, msg_parg)) { + if (optee->ops->do_call_with_arg(ctx, shm)) { msg_arg->ret = TEEC_ERROR_COMMUNICATION; msg_arg->ret_origin = TEEC_ORIGIN_COMMS; } - if (optee_from_msg_param(param, arg->num_params, msg_arg->params)) { + if (optee->ops->from_msg_param(optee, param, arg->num_params, + msg_arg->params)) { msg_arg->ret = TEEC_ERROR_COMMUNICATION; msg_arg->ret_origin = TEEC_ORIGIN_COMMS; } @@ -366,10 +304,10 @@ out: int optee_cancel_req(struct tee_context *ctx, u32 cancel_id, u32 session) { + struct optee *optee = tee_get_drvdata(ctx->teedev); struct optee_context_data *ctxdata = ctx->data; struct tee_shm *shm; struct optee_msg_arg *msg_arg; - phys_addr_t msg_parg; struct optee_session *sess; /* Check that the session is valid */ @@ -379,195 +317,19 @@ int optee_cancel_req(struct tee_context *ctx, u32 cancel_id, u32 session) if (!sess) return -EINVAL; - shm = get_msg_arg(ctx, 0, &msg_arg, &msg_parg); + shm = optee_get_msg_arg(ctx, 0, &msg_arg); if (IS_ERR(shm)) return PTR_ERR(shm); msg_arg->cmd = OPTEE_MSG_CMD_CANCEL; msg_arg->session = session; msg_arg->cancel_id = cancel_id; - optee_do_call_with_arg(ctx, msg_parg); + optee->ops->do_call_with_arg(ctx, shm); tee_shm_free(shm); return 0; } -/** - * optee_enable_shm_cache() - Enables caching of some shared memory allocation - * in OP-TEE - * @optee: main service struct - */ -void optee_enable_shm_cache(struct optee *optee) -{ - struct optee_call_waiter w; - - /* We need to retry until secure world isn't busy. */ - optee_cq_wait_init(&optee->call_queue, &w); - while (true) { - struct arm_smccc_res res; - - optee->invoke_fn(OPTEE_SMC_ENABLE_SHM_CACHE, 0, 0, 0, 0, 0, 0, - 0, &res); - if (res.a0 == OPTEE_SMC_RETURN_OK) - break; - optee_cq_wait_for_completion(&optee->call_queue, &w); - } - optee_cq_wait_final(&optee->call_queue, &w); -} - -/** - * __optee_disable_shm_cache() - Disables caching of some shared memory - * allocation in OP-TEE - * @optee: main service struct - * @is_mapped: true if the cached shared memory addresses were mapped by this - * kernel, are safe to dereference, and should be freed - */ -static void __optee_disable_shm_cache(struct optee *optee, bool is_mapped) -{ - struct optee_call_waiter w; - - /* We need to retry until secure world isn't busy. */ - optee_cq_wait_init(&optee->call_queue, &w); - while (true) { - union { - struct arm_smccc_res smccc; - struct optee_smc_disable_shm_cache_result result; - } res; - - optee->invoke_fn(OPTEE_SMC_DISABLE_SHM_CACHE, 0, 0, 0, 0, 0, 0, - 0, &res.smccc); - if (res.result.status == OPTEE_SMC_RETURN_ENOTAVAIL) - break; /* All shm's freed */ - if (res.result.status == OPTEE_SMC_RETURN_OK) { - struct tee_shm *shm; - - /* - * Shared memory references that were not mapped by - * this kernel must be ignored to prevent a crash. - */ - if (!is_mapped) - continue; - - shm = reg_pair_to_ptr(res.result.shm_upper32, - res.result.shm_lower32); - tee_shm_free(shm); - } else { - optee_cq_wait_for_completion(&optee->call_queue, &w); - } - } - optee_cq_wait_final(&optee->call_queue, &w); -} - -/** - * optee_disable_shm_cache() - Disables caching of mapped shared memory - * allocations in OP-TEE - * @optee: main service struct - */ -void optee_disable_shm_cache(struct optee *optee) -{ - return __optee_disable_shm_cache(optee, true); -} - -/** - * optee_disable_unmapped_shm_cache() - Disables caching of shared memory - * allocations in OP-TEE which are not - * currently mapped - * @optee: main service struct - */ -void optee_disable_unmapped_shm_cache(struct optee *optee) -{ - return __optee_disable_shm_cache(optee, false); -} - -#define PAGELIST_ENTRIES_PER_PAGE \ - ((OPTEE_MSG_NONCONTIG_PAGE_SIZE / sizeof(u64)) - 1) - -/** - * optee_fill_pages_list() - write list of user pages to given shared - * buffer. - * - * @dst: page-aligned buffer where list of pages will be stored - * @pages: array of pages that represents shared buffer - * @num_pages: number of entries in @pages - * @page_offset: offset of user buffer from page start - * - * @dst should be big enough to hold list of user page addresses and - * links to the next pages of buffer - */ -void optee_fill_pages_list(u64 *dst, struct page **pages, int num_pages, - size_t page_offset) -{ - int n = 0; - phys_addr_t optee_page; - /* - * Refer to OPTEE_MSG_ATTR_NONCONTIG description in optee_msg.h - * for details. - */ - struct { - u64 pages_list[PAGELIST_ENTRIES_PER_PAGE]; - u64 next_page_data; - } *pages_data; - - /* - * Currently OP-TEE uses 4k page size and it does not looks - * like this will change in the future. On other hand, there are - * no know ARM architectures with page size < 4k. - * Thus the next built assert looks redundant. But the following - * code heavily relies on this assumption, so it is better be - * safe than sorry. - */ - BUILD_BUG_ON(PAGE_SIZE < OPTEE_MSG_NONCONTIG_PAGE_SIZE); - - pages_data = (void *)dst; - /* - * If linux page is bigger than 4k, and user buffer offset is - * larger than 4k/8k/12k/etc this will skip first 4k pages, - * because they bear no value data for OP-TEE. - */ - optee_page = page_to_phys(*pages) + - round_down(page_offset, OPTEE_MSG_NONCONTIG_PAGE_SIZE); - - while (true) { - pages_data->pages_list[n++] = optee_page; - - if (n == PAGELIST_ENTRIES_PER_PAGE) { - pages_data->next_page_data = - virt_to_phys(pages_data + 1); - pages_data++; - n = 0; - } - - optee_page += OPTEE_MSG_NONCONTIG_PAGE_SIZE; - if (!(optee_page & ~PAGE_MASK)) { - if (!--num_pages) - break; - pages++; - optee_page = page_to_phys(*pages); - } - } -} - -/* - * The final entry in each pagelist page is a pointer to the next - * pagelist page. - */ -static size_t get_pages_list_size(size_t num_entries) -{ - int pages = DIV_ROUND_UP(num_entries, PAGELIST_ENTRIES_PER_PAGE); - - return pages * OPTEE_MSG_NONCONTIG_PAGE_SIZE; -} - -u64 *optee_allocate_pages_list(size_t num_entries) -{ - return alloc_pages_exact(get_pages_list_size(num_entries), GFP_KERNEL); -} - -void optee_free_pages_list(void *list, size_t num_entries) -{ - free_pages_exact(list, get_pages_list_size(num_entries)); -} - static bool is_normal_memory(pgprot_t p) { #if defined(CONFIG_ARM) @@ -591,7 +353,7 @@ static int __check_mem_type(struct vm_area_struct *vma, unsigned long end) return -EINVAL; } -static int check_mem_type(unsigned long start, size_t num_pages) +int optee_check_mem_type(unsigned long start, size_t num_pages) { struct mm_struct *mm = current->mm; int rc; @@ -610,94 +372,3 @@ static int check_mem_type(unsigned long start, size_t num_pages) return rc; } - -int optee_shm_register(struct tee_context *ctx, struct tee_shm *shm, - struct page **pages, size_t num_pages, - unsigned long start) -{ - struct tee_shm *shm_arg = NULL; - struct optee_msg_arg *msg_arg; - u64 *pages_list; - phys_addr_t msg_parg; - int rc; - - if (!num_pages) - return -EINVAL; - - rc = check_mem_type(start, num_pages); - if (rc) - return rc; - - pages_list = optee_allocate_pages_list(num_pages); - if (!pages_list) - return -ENOMEM; - - shm_arg = get_msg_arg(ctx, 1, &msg_arg, &msg_parg); - if (IS_ERR(shm_arg)) { - rc = PTR_ERR(shm_arg); - goto out; - } - - optee_fill_pages_list(pages_list, pages, num_pages, - tee_shm_get_page_offset(shm)); - - msg_arg->cmd = OPTEE_MSG_CMD_REGISTER_SHM; - msg_arg->params->attr = OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | - OPTEE_MSG_ATTR_NONCONTIG; - msg_arg->params->u.tmem.shm_ref = (unsigned long)shm; - msg_arg->params->u.tmem.size = tee_shm_get_size(shm); - /* - * In the least bits of msg_arg->params->u.tmem.buf_ptr we - * store buffer offset from 4k page, as described in OP-TEE ABI. - */ - msg_arg->params->u.tmem.buf_ptr = virt_to_phys(pages_list) | - (tee_shm_get_page_offset(shm) & (OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1)); - - if (optee_do_call_with_arg(ctx, msg_parg) || - msg_arg->ret != TEEC_SUCCESS) - rc = -EINVAL; - - tee_shm_free(shm_arg); -out: - optee_free_pages_list(pages_list, num_pages); - return rc; -} - -int optee_shm_unregister(struct tee_context *ctx, struct tee_shm *shm) -{ - struct tee_shm *shm_arg; - struct optee_msg_arg *msg_arg; - phys_addr_t msg_parg; - int rc = 0; - - shm_arg = get_msg_arg(ctx, 1, &msg_arg, &msg_parg); - if (IS_ERR(shm_arg)) - return PTR_ERR(shm_arg); - - msg_arg->cmd = OPTEE_MSG_CMD_UNREGISTER_SHM; - - msg_arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_RMEM_INPUT; - msg_arg->params[0].u.rmem.shm_ref = (unsigned long)shm; - - if (optee_do_call_with_arg(ctx, msg_parg) || - msg_arg->ret != TEEC_SUCCESS) - rc = -EINVAL; - tee_shm_free(shm_arg); - return rc; -} - -int optee_shm_register_supp(struct tee_context *ctx, struct tee_shm *shm, - struct page **pages, size_t num_pages, - unsigned long start) -{ - /* - * We don't want to register supplicant memory in OP-TEE. - * Instead information about it will be passed in RPC code. - */ - return check_mem_type(start, num_pages); -} - -int optee_shm_unregister_supp(struct tee_context *ctx, struct tee_shm *shm) -{ - return 0; -} diff --git a/drivers/tee/optee/core.c b/drivers/tee/optee/core.c index 5363ebebfc35..ab2edfcc6c70 100644 --- a/drivers/tee/optee/core.c +++ b/drivers/tee/optee/core.c @@ -1,215 +1,71 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2015-2021, Linaro Limited + * Copyright (c) 2016, EPAM Systems */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/arm-smccc.h> #include <linux/crash_dump.h> #include <linux/errno.h> #include <linux/io.h> +#include <linux/mm.h> #include <linux/module.h> -#include <linux/of.h> -#include <linux/of_platform.h> -#include <linux/platform_device.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/tee_drv.h> #include <linux/types.h> -#include <linux/uaccess.h> #include <linux/workqueue.h> #include "optee_private.h" -#include "optee_smc.h" -#include "shm_pool.h" -#define DRIVER_NAME "optee" - -#define OPTEE_SHM_NUM_PRIV_PAGES CONFIG_OPTEE_SHM_NUM_PRIV_PAGES - -/** - * optee_from_msg_param() - convert from OPTEE_MSG parameters to - * struct tee_param - * @params: subsystem internal parameter representation - * @num_params: number of elements in the parameter arrays - * @msg_params: OPTEE_MSG parameters - * Returns 0 on success or <0 on failure - */ -int optee_from_msg_param(struct tee_param *params, size_t num_params, - const struct optee_msg_param *msg_params) +int optee_pool_op_alloc_helper(struct tee_shm_pool_mgr *poolm, + struct tee_shm *shm, size_t size, + int (*shm_register)(struct tee_context *ctx, + struct tee_shm *shm, + struct page **pages, + size_t num_pages, + unsigned long start)) { - int rc; - size_t n; - struct tee_shm *shm; - phys_addr_t pa; - - for (n = 0; n < num_params; n++) { - struct tee_param *p = params + n; - const struct optee_msg_param *mp = msg_params + n; - u32 attr = mp->attr & OPTEE_MSG_ATTR_TYPE_MASK; - - switch (attr) { - case OPTEE_MSG_ATTR_TYPE_NONE: - p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; - memset(&p->u, 0, sizeof(p->u)); - break; - case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT: - case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT: - case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT: - p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT + - attr - OPTEE_MSG_ATTR_TYPE_VALUE_INPUT; - p->u.value.a = mp->u.value.a; - p->u.value.b = mp->u.value.b; - p->u.value.c = mp->u.value.c; - break; - case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT: - case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT: - case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT: - p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT + - attr - OPTEE_MSG_ATTR_TYPE_TMEM_INPUT; - p->u.memref.size = mp->u.tmem.size; - shm = (struct tee_shm *)(unsigned long) - mp->u.tmem.shm_ref; - if (!shm) { - p->u.memref.shm_offs = 0; - p->u.memref.shm = NULL; - break; - } - rc = tee_shm_get_pa(shm, 0, &pa); - if (rc) - return rc; - p->u.memref.shm_offs = mp->u.tmem.buf_ptr - pa; - p->u.memref.shm = shm; - break; - case OPTEE_MSG_ATTR_TYPE_RMEM_INPUT: - case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT: - case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT: - p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT + - attr - OPTEE_MSG_ATTR_TYPE_RMEM_INPUT; - p->u.memref.size = mp->u.rmem.size; - shm = (struct tee_shm *)(unsigned long) - mp->u.rmem.shm_ref; - - if (!shm) { - p->u.memref.shm_offs = 0; - p->u.memref.shm = NULL; - break; - } - p->u.memref.shm_offs = mp->u.rmem.offs; - p->u.memref.shm = shm; + unsigned int order = get_order(size); + struct page *page; + int rc = 0; - break; + page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); + if (!page) + return -ENOMEM; - default: - return -EINVAL; - } - } - return 0; -} + shm->kaddr = page_address(page); + shm->paddr = page_to_phys(page); + shm->size = PAGE_SIZE << order; -static int to_msg_param_tmp_mem(struct optee_msg_param *mp, - const struct tee_param *p) -{ - int rc; - phys_addr_t pa; + if (shm_register) { + unsigned int nr_pages = 1 << order, i; + struct page **pages; - mp->attr = OPTEE_MSG_ATTR_TYPE_TMEM_INPUT + p->attr - - TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL); + if (!pages) { + rc = -ENOMEM; + goto err; + } - mp->u.tmem.shm_ref = (unsigned long)p->u.memref.shm; - mp->u.tmem.size = p->u.memref.size; + for (i = 0; i < nr_pages; i++) { + pages[i] = page; + page++; + } - if (!p->u.memref.shm) { - mp->u.tmem.buf_ptr = 0; - return 0; + shm->flags |= TEE_SHM_REGISTER; + rc = shm_register(shm->ctx, shm, pages, nr_pages, + (unsigned long)shm->kaddr); + kfree(pages); + if (rc) + goto err; } - rc = tee_shm_get_pa(p->u.memref.shm, p->u.memref.shm_offs, &pa); - if (rc) - return rc; - - mp->u.tmem.buf_ptr = pa; - mp->attr |= OPTEE_MSG_ATTR_CACHE_PREDEFINED << - OPTEE_MSG_ATTR_CACHE_SHIFT; - return 0; -} - -static int to_msg_param_reg_mem(struct optee_msg_param *mp, - const struct tee_param *p) -{ - mp->attr = OPTEE_MSG_ATTR_TYPE_RMEM_INPUT + p->attr - - TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; - mp->u.rmem.shm_ref = (unsigned long)p->u.memref.shm; - mp->u.rmem.size = p->u.memref.size; - mp->u.rmem.offs = p->u.memref.shm_offs; - return 0; -} - -/** - * optee_to_msg_param() - convert from struct tee_params to OPTEE_MSG parameters - * @msg_params: OPTEE_MSG parameters - * @num_params: number of elements in the parameter arrays - * @params: subsystem itnernal parameter representation - * Returns 0 on success or <0 on failure - */ -int optee_to_msg_param(struct optee_msg_param *msg_params, size_t num_params, - const struct tee_param *params) -{ - int rc; - size_t n; - - for (n = 0; n < num_params; n++) { - const struct tee_param *p = params + n; - struct optee_msg_param *mp = msg_params + n; - - switch (p->attr) { - case TEE_IOCTL_PARAM_ATTR_TYPE_NONE: - mp->attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; - memset(&mp->u, 0, sizeof(mp->u)); - break; - case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT: - case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT: - case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT: - mp->attr = OPTEE_MSG_ATTR_TYPE_VALUE_INPUT + p->attr - - TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; - mp->u.value.a = p->u.value.a; - mp->u.value.b = p->u.value.b; - mp->u.value.c = p->u.value.c; - break; - case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT: - case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: - case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: - if (tee_shm_is_registered(p->u.memref.shm)) - rc = to_msg_param_reg_mem(mp, p); - else - rc = to_msg_param_tmp_mem(mp, p); - if (rc) - return rc; - break; - default: - return -EINVAL; - } - } - return 0; -} - -static void optee_get_version(struct tee_device *teedev, - struct tee_ioctl_version_data *vers) -{ - struct tee_ioctl_version_data v = { - .impl_id = TEE_IMPL_ID_OPTEE, - .impl_caps = TEE_OPTEE_CAP_TZ, - .gen_caps = TEE_GEN_CAP_GP, - }; - struct optee *optee = tee_get_drvdata(teedev); - - if (optee->sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) - v.gen_caps |= TEE_GEN_CAP_REG_MEM; - if (optee->sec_caps & OPTEE_SMC_SEC_CAP_MEMREF_NULL) - v.gen_caps |= TEE_GEN_CAP_MEMREF_NULL; - *vers = v; +err: + __free_pages(page, order); + return rc; } static void optee_bus_scan(struct work_struct *work) @@ -217,7 +73,7 @@ static void optee_bus_scan(struct work_struct *work) WARN_ON(optee_enumerate_devices(PTA_CMD_GET_DEVICES_SUPP)); } -static int optee_open(struct tee_context *ctx) +int optee_open(struct tee_context *ctx, bool cap_memref_null) { struct optee_context_data *ctxdata; struct tee_device *teedev = ctx->teedev; @@ -255,347 +111,55 @@ static int optee_open(struct tee_context *ctx) mutex_init(&ctxdata->mutex); INIT_LIST_HEAD(&ctxdata->sess_list); - if (optee->sec_caps & OPTEE_SMC_SEC_CAP_MEMREF_NULL) - ctx->cap_memref_null = true; - else - ctx->cap_memref_null = false; - + ctx->cap_memref_null = cap_memref_null; ctx->data = ctxdata; return 0; } -static void optee_release(struct tee_context *ctx) +static void optee_release_helper(struct tee_context *ctx, + int (*close_session)(struct tee_context *ctx, + u32 session)) { struct optee_context_data *ctxdata = ctx->data; - struct tee_device *teedev = ctx->teedev; - struct optee *optee = tee_get_drvdata(teedev); - struct tee_shm *shm; - struct optee_msg_arg *arg = NULL; - phys_addr_t parg; struct optee_session *sess; struct optee_session *sess_tmp; if (!ctxdata) return; - shm = tee_shm_alloc(ctx, sizeof(struct optee_msg_arg), - TEE_SHM_MAPPED | TEE_SHM_PRIV); - if (!IS_ERR(shm)) { - arg = tee_shm_get_va(shm, 0); - /* - * If va2pa fails for some reason, we can't call into - * secure world, only free the memory. Secure OS will leak - * sessions and finally refuse more sessions, but we will - * at least let normal world reclaim its memory. - */ - if (!IS_ERR(arg)) - if (tee_shm_va2pa(shm, arg, &parg)) - arg = NULL; /* prevent usage of parg below */ - } - list_for_each_entry_safe(sess, sess_tmp, &ctxdata->sess_list, list_node) { list_del(&sess->list_node); - if (!IS_ERR_OR_NULL(arg)) { - memset(arg, 0, sizeof(*arg)); - arg->cmd = OPTEE_MSG_CMD_CLOSE_SESSION; - arg->session = sess->session_id; - optee_do_call_with_arg(ctx, parg); - } + close_session(ctx, sess->session_id); kfree(sess); } kfree(ctxdata); - - if (!IS_ERR(shm)) - tee_shm_free(shm); - ctx->data = NULL; - - if (teedev == optee->supp_teedev) { - if (optee->scan_bus_wq) { - destroy_workqueue(optee->scan_bus_wq); - optee->scan_bus_wq = NULL; - } - optee_supp_release(&optee->supp); - } -} - -static const struct tee_driver_ops optee_ops = { - .get_version = optee_get_version, - .open = optee_open, - .release = optee_release, - .open_session = optee_open_session, - .close_session = optee_close_session, - .invoke_func = optee_invoke_func, - .cancel_req = optee_cancel_req, - .shm_register = optee_shm_register, - .shm_unregister = optee_shm_unregister, -}; - -static const struct tee_desc optee_desc = { - .name = DRIVER_NAME "-clnt", - .ops = &optee_ops, - .owner = THIS_MODULE, -}; - -static const struct tee_driver_ops optee_supp_ops = { - .get_version = optee_get_version, - .open = optee_open, - .release = optee_release, - .supp_recv = optee_supp_recv, - .supp_send = optee_supp_send, - .shm_register = optee_shm_register_supp, - .shm_unregister = optee_shm_unregister_supp, -}; - -static const struct tee_desc optee_supp_desc = { - .name = DRIVER_NAME "-supp", - .ops = &optee_supp_ops, - .owner = THIS_MODULE, - .flags = TEE_DESC_PRIVILEGED, -}; - -static bool optee_msg_api_uid_is_optee_api(optee_invoke_fn *invoke_fn) -{ - struct arm_smccc_res res; - - invoke_fn(OPTEE_SMC_CALLS_UID, 0, 0, 0, 0, 0, 0, 0, &res); - - if (res.a0 == OPTEE_MSG_UID_0 && res.a1 == OPTEE_MSG_UID_1 && - res.a2 == OPTEE_MSG_UID_2 && res.a3 == OPTEE_MSG_UID_3) - return true; - return false; -} - -static void optee_msg_get_os_revision(optee_invoke_fn *invoke_fn) -{ - union { - struct arm_smccc_res smccc; - struct optee_smc_call_get_os_revision_result result; - } res = { - .result = { - .build_id = 0 - } - }; - - invoke_fn(OPTEE_SMC_CALL_GET_OS_REVISION, 0, 0, 0, 0, 0, 0, 0, - &res.smccc); - - if (res.result.build_id) - pr_info("revision %lu.%lu (%08lx)", res.result.major, - res.result.minor, res.result.build_id); - else - pr_info("revision %lu.%lu", res.result.major, res.result.minor); -} - -static bool optee_msg_api_revision_is_compatible(optee_invoke_fn *invoke_fn) -{ - union { - struct arm_smccc_res smccc; - struct optee_smc_calls_revision_result result; - } res; - - invoke_fn(OPTEE_SMC_CALLS_REVISION, 0, 0, 0, 0, 0, 0, 0, &res.smccc); - - if (res.result.major == OPTEE_MSG_REVISION_MAJOR && - (int)res.result.minor >= OPTEE_MSG_REVISION_MINOR) - return true; - return false; -} - -static bool optee_msg_exchange_capabilities(optee_invoke_fn *invoke_fn, - u32 *sec_caps) -{ - union { - struct arm_smccc_res smccc; - struct optee_smc_exchange_capabilities_result result; - } res; - u32 a1 = 0; - - /* - * TODO This isn't enough to tell if it's UP system (from kernel - * point of view) or not, is_smp() returns the the information - * needed, but can't be called directly from here. - */ - if (!IS_ENABLED(CONFIG_SMP) || nr_cpu_ids == 1) - a1 |= OPTEE_SMC_NSEC_CAP_UNIPROCESSOR; - - invoke_fn(OPTEE_SMC_EXCHANGE_CAPABILITIES, a1, 0, 0, 0, 0, 0, 0, - &res.smccc); - - if (res.result.status != OPTEE_SMC_RETURN_OK) - return false; - - *sec_caps = res.result.capabilities; - return true; -} - -static struct tee_shm_pool *optee_config_dyn_shm(void) -{ - struct tee_shm_pool_mgr *priv_mgr; - struct tee_shm_pool_mgr *dmabuf_mgr; - void *rc; - - rc = optee_shm_pool_alloc_pages(); - if (IS_ERR(rc)) - return rc; - priv_mgr = rc; - - rc = optee_shm_pool_alloc_pages(); - if (IS_ERR(rc)) { - tee_shm_pool_mgr_destroy(priv_mgr); - return rc; - } - dmabuf_mgr = rc; - - rc = tee_shm_pool_alloc(priv_mgr, dmabuf_mgr); - if (IS_ERR(rc)) { - tee_shm_pool_mgr_destroy(priv_mgr); - tee_shm_pool_mgr_destroy(dmabuf_mgr); - } - - return rc; } -static struct tee_shm_pool * -optee_config_shm_memremap(optee_invoke_fn *invoke_fn, void **memremaped_shm) +void optee_release(struct tee_context *ctx) { - union { - struct arm_smccc_res smccc; - struct optee_smc_get_shm_config_result result; - } res; - unsigned long vaddr; - phys_addr_t paddr; - size_t size; - phys_addr_t begin; - phys_addr_t end; - void *va; - struct tee_shm_pool_mgr *priv_mgr; - struct tee_shm_pool_mgr *dmabuf_mgr; - void *rc; - const int sz = OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE; - - invoke_fn(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc); - if (res.result.status != OPTEE_SMC_RETURN_OK) { - pr_err("static shm service not available\n"); - return ERR_PTR(-ENOENT); - } - - if (res.result.settings != OPTEE_SMC_SHM_CACHED) { - pr_err("only normal cached shared memory supported\n"); - return ERR_PTR(-EINVAL); - } - - begin = roundup(res.result.start, PAGE_SIZE); - end = rounddown(res.result.start + res.result.size, PAGE_SIZE); - paddr = begin; - size = end - begin; - - if (size < 2 * OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE) { - pr_err("too small shared memory area\n"); - return ERR_PTR(-EINVAL); - } - - va = memremap(paddr, size, MEMREMAP_WB); - if (!va) { - pr_err("shared memory ioremap failed\n"); - return ERR_PTR(-EINVAL); - } - vaddr = (unsigned long)va; - - rc = tee_shm_pool_mgr_alloc_res_mem(vaddr, paddr, sz, - 3 /* 8 bytes aligned */); - if (IS_ERR(rc)) - goto err_memunmap; - priv_mgr = rc; - - vaddr += sz; - paddr += sz; - size -= sz; - - rc = tee_shm_pool_mgr_alloc_res_mem(vaddr, paddr, size, PAGE_SHIFT); - if (IS_ERR(rc)) - goto err_free_priv_mgr; - dmabuf_mgr = rc; - - rc = tee_shm_pool_alloc(priv_mgr, dmabuf_mgr); - if (IS_ERR(rc)) - goto err_free_dmabuf_mgr; - - *memremaped_shm = va; - - return rc; - -err_free_dmabuf_mgr: - tee_shm_pool_mgr_destroy(dmabuf_mgr); -err_free_priv_mgr: - tee_shm_pool_mgr_destroy(priv_mgr); -err_memunmap: - memunmap(va); - return rc; + optee_release_helper(ctx, optee_close_session_helper); } -/* Simple wrapper functions to be able to use a function pointer */ -static void optee_smccc_smc(unsigned long a0, unsigned long a1, - unsigned long a2, unsigned long a3, - unsigned long a4, unsigned long a5, - unsigned long a6, unsigned long a7, - struct arm_smccc_res *res) +void optee_release_supp(struct tee_context *ctx) { - arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res); -} + struct optee *optee = tee_get_drvdata(ctx->teedev); -static void optee_smccc_hvc(unsigned long a0, unsigned long a1, - unsigned long a2, unsigned long a3, - unsigned long a4, unsigned long a5, - unsigned long a6, unsigned long a7, - struct arm_smccc_res *res) -{ - arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); -} - -static optee_invoke_fn *get_invoke_func(struct device *dev) -{ - const char *method; - - pr_info("probing for conduit method.\n"); - - if (device_property_read_string(dev, "method", &method)) { - pr_warn("missing \"method\" property\n"); - return ERR_PTR(-ENXIO); + optee_release_helper(ctx, optee_close_session_helper); + if (optee->scan_bus_wq) { + destroy_workqueue(optee->scan_bus_wq); + optee->scan_bus_wq = NULL; } - - if (!strcmp("hvc", method)) - return optee_smccc_hvc; - else if (!strcmp("smc", method)) - return optee_smccc_smc; - - pr_warn("invalid \"method\" property: %s\n", method); - return ERR_PTR(-EINVAL); + optee_supp_release(&optee->supp); } -/* optee_remove - Device Removal Routine - * @pdev: platform device information struct - * - * optee_remove is called by platform subsystem to alert the driver - * that it should release the device - */ - -static int optee_remove(struct platform_device *pdev) +void optee_remove_common(struct optee *optee) { - struct optee *optee = platform_get_drvdata(pdev); - /* Unregister OP-TEE specific client devices on TEE bus */ optee_unregister_devices(); /* - * Ask OP-TEE to free all cached shared memory objects to decrease - * reference counters and also avoid wild pointers in secure world - * into the old shared memory range. - */ - optee_disable_shm_cache(optee); - - /* * The two devices have to be unregistered before we can free the * other resources. */ @@ -603,39 +167,16 @@ static int optee_remove(struct platform_device *pdev) tee_device_unregister(optee->teedev); tee_shm_pool_free(optee->pool); - if (optee->memremaped_shm) - memunmap(optee->memremaped_shm); optee_wait_queue_exit(&optee->wait_queue); optee_supp_uninit(&optee->supp); mutex_destroy(&optee->call_queue.mutex); - - kfree(optee); - - return 0; } -/* optee_shutdown - Device Removal Routine - * @pdev: platform device information struct - * - * platform_shutdown is called by the platform subsystem to alert - * the driver that a shutdown, reboot, or kexec is happening and - * device must be disabled. - */ -static void optee_shutdown(struct platform_device *pdev) -{ - optee_disable_shm_cache(platform_get_drvdata(pdev)); -} +static int smc_abi_rc; +static int ffa_abi_rc; -static int optee_probe(struct platform_device *pdev) +static int optee_core_init(void) { - optee_invoke_fn *invoke_fn; - struct tee_shm_pool *pool = ERR_PTR(-EINVAL); - struct optee *optee = NULL; - void *memremaped_shm = NULL; - struct tee_device *teedev; - u32 sec_caps; - int rc; - /* * The kernel may have crashed at the same time that all available * secure world threads were suspended and we cannot reschedule the @@ -646,138 +187,24 @@ static int optee_probe(struct platform_device *pdev) if (is_kdump_kernel()) return -ENODEV; - invoke_fn = get_invoke_func(&pdev->dev); - if (IS_ERR(invoke_fn)) - return PTR_ERR(invoke_fn); - - if (!optee_msg_api_uid_is_optee_api(invoke_fn)) { - pr_warn("api uid mismatch\n"); - return -EINVAL; - } - - optee_msg_get_os_revision(invoke_fn); - - if (!optee_msg_api_revision_is_compatible(invoke_fn)) { - pr_warn("api revision mismatch\n"); - return -EINVAL; - } - - if (!optee_msg_exchange_capabilities(invoke_fn, &sec_caps)) { - pr_warn("capabilities mismatch\n"); - return -EINVAL; - } - - /* - * Try to use dynamic shared memory if possible - */ - if (sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) - pool = optee_config_dyn_shm(); - - /* - * If dynamic shared memory is not available or failed - try static one - */ - if (IS_ERR(pool) && (sec_caps & OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM)) - pool = optee_config_shm_memremap(invoke_fn, &memremaped_shm); - - if (IS_ERR(pool)) - return PTR_ERR(pool); - - optee = kzalloc(sizeof(*optee), GFP_KERNEL); - if (!optee) { - rc = -ENOMEM; - goto err; - } - - optee->invoke_fn = invoke_fn; - optee->sec_caps = sec_caps; - - teedev = tee_device_alloc(&optee_desc, NULL, pool, optee); - if (IS_ERR(teedev)) { - rc = PTR_ERR(teedev); - goto err; - } - optee->teedev = teedev; - - teedev = tee_device_alloc(&optee_supp_desc, NULL, pool, optee); - if (IS_ERR(teedev)) { - rc = PTR_ERR(teedev); - goto err; - } - optee->supp_teedev = teedev; - - rc = tee_device_register(optee->teedev); - if (rc) - goto err; - - rc = tee_device_register(optee->supp_teedev); - if (rc) - goto err; - - mutex_init(&optee->call_queue.mutex); - INIT_LIST_HEAD(&optee->call_queue.waiters); - optee_wait_queue_init(&optee->wait_queue); - optee_supp_init(&optee->supp); - optee->memremaped_shm = memremaped_shm; - optee->pool = pool; - - /* - * Ensure that there are no pre-existing shm objects before enabling - * the shm cache so that there's no chance of receiving an invalid - * address during shutdown. This could occur, for example, if we're - * kexec booting from an older kernel that did not properly cleanup the - * shm cache. - */ - optee_disable_unmapped_shm_cache(optee); - - optee_enable_shm_cache(optee); + smc_abi_rc = optee_smc_abi_register(); + ffa_abi_rc = optee_ffa_abi_register(); - if (optee->sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) - pr_info("dynamic shared memory is enabled\n"); - - platform_set_drvdata(pdev, optee); - - rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); - if (rc) { - optee_remove(pdev); - return rc; - } - - pr_info("initialized driver\n"); + /* If both failed there's no point with this module */ + if (smc_abi_rc && ffa_abi_rc) + return smc_abi_rc; return 0; -err: - if (optee) { - /* - * tee_device_unregister() is safe to call even if the - * devices hasn't been registered with - * tee_device_register() yet. - */ - tee_device_unregister(optee->supp_teedev); - tee_device_unregister(optee->teedev); - kfree(optee); - } - if (pool) - tee_shm_pool_free(pool); - if (memremaped_shm) - memunmap(memremaped_shm); - return rc; } +module_init(optee_core_init); -static const struct of_device_id optee_dt_match[] = { - { .compatible = "linaro,optee-tz" }, - {}, -}; -MODULE_DEVICE_TABLE(of, optee_dt_match); - -static struct platform_driver optee_driver = { - .probe = optee_probe, - .remove = optee_remove, - .shutdown = optee_shutdown, - .driver = { - .name = "optee", - .of_match_table = optee_dt_match, - }, -}; -module_platform_driver(optee_driver); +static void optee_core_exit(void) +{ + if (!smc_abi_rc) + optee_smc_abi_unregister(); + if (!ffa_abi_rc) + optee_ffa_abi_unregister(); +} +module_exit(optee_core_exit); MODULE_AUTHOR("Linaro"); MODULE_DESCRIPTION("OP-TEE driver"); diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c new file mode 100644 index 000000000000..45424824e0f9 --- /dev/null +++ b/drivers/tee/optee/ffa_abi.c @@ -0,0 +1,911 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, Linaro Limited + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/arm_ffa.h> +#include <linux/errno.h> +#include <linux/scatterlist.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/tee_drv.h> +#include <linux/types.h> +#include "optee_private.h" +#include "optee_ffa.h" +#include "optee_rpc_cmd.h" + +/* + * This file implement the FF-A ABI used when communicating with secure world + * OP-TEE OS via FF-A. + * This file is divided into the following sections: + * 1. Maintain a hash table for lookup of a global FF-A memory handle + * 2. Convert between struct tee_param and struct optee_msg_param + * 3. Low level support functions to register shared memory in secure world + * 4. Dynamic shared memory pool based on alloc_pages() + * 5. Do a normal scheduled call into secure world + * 6. Driver initialization. + */ + +/* + * 1. Maintain a hash table for lookup of a global FF-A memory handle + * + * FF-A assigns a global memory handle for each piece shared memory. + * This handle is then used when communicating with secure world. + * + * Main functions are optee_shm_add_ffa_handle() and optee_shm_rem_ffa_handle() + */ +struct shm_rhash { + struct tee_shm *shm; + u64 global_id; + struct rhash_head linkage; +}; + +static void rh_free_fn(void *ptr, void *arg) +{ + kfree(ptr); +} + +static const struct rhashtable_params shm_rhash_params = { + .head_offset = offsetof(struct shm_rhash, linkage), + .key_len = sizeof(u64), + .key_offset = offsetof(struct shm_rhash, global_id), + .automatic_shrinking = true, +}; + +static struct tee_shm *optee_shm_from_ffa_handle(struct optee *optee, + u64 global_id) +{ + struct tee_shm *shm = NULL; + struct shm_rhash *r; + + mutex_lock(&optee->ffa.mutex); + r = rhashtable_lookup_fast(&optee->ffa.global_ids, &global_id, + shm_rhash_params); + if (r) + shm = r->shm; + mutex_unlock(&optee->ffa.mutex); + + return shm; +} + +static int optee_shm_add_ffa_handle(struct optee *optee, struct tee_shm *shm, + u64 global_id) +{ + struct shm_rhash *r; + int rc; + + r = kmalloc(sizeof(*r), GFP_KERNEL); + if (!r) + return -ENOMEM; + r->shm = shm; + r->global_id = global_id; + + mutex_lock(&optee->ffa.mutex); + rc = rhashtable_lookup_insert_fast(&optee->ffa.global_ids, &r->linkage, + shm_rhash_params); + mutex_unlock(&optee->ffa.mutex); + + if (rc) + kfree(r); + + return rc; +} + +static int optee_shm_rem_ffa_handle(struct optee *optee, u64 global_id) +{ + struct shm_rhash *r; + int rc = -ENOENT; + + mutex_lock(&optee->ffa.mutex); + r = rhashtable_lookup_fast(&optee->ffa.global_ids, &global_id, + shm_rhash_params); + if (r) + rc = rhashtable_remove_fast(&optee->ffa.global_ids, + &r->linkage, shm_rhash_params); + mutex_unlock(&optee->ffa.mutex); + + if (!rc) + kfree(r); + + return rc; +} + +/* + * 2. Convert between struct tee_param and struct optee_msg_param + * + * optee_ffa_from_msg_param() and optee_ffa_to_msg_param() are the main + * functions. + */ + +static void from_msg_param_ffa_mem(struct optee *optee, struct tee_param *p, + u32 attr, const struct optee_msg_param *mp) +{ + struct tee_shm *shm = NULL; + u64 offs_high = 0; + u64 offs_low = 0; + + p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT + + attr - OPTEE_MSG_ATTR_TYPE_FMEM_INPUT; + p->u.memref.size = mp->u.fmem.size; + + if (mp->u.fmem.global_id != OPTEE_MSG_FMEM_INVALID_GLOBAL_ID) + shm = optee_shm_from_ffa_handle(optee, mp->u.fmem.global_id); + p->u.memref.shm = shm; + + if (shm) { + offs_low = mp->u.fmem.offs_low; + offs_high = mp->u.fmem.offs_high; + } + p->u.memref.shm_offs = offs_low | offs_high << 32; +} + +/** + * optee_ffa_from_msg_param() - convert from OPTEE_MSG parameters to + * struct tee_param + * @optee: main service struct + * @params: subsystem internal parameter representation + * @num_params: number of elements in the parameter arrays + * @msg_params: OPTEE_MSG parameters + * + * Returns 0 on success or <0 on failure + */ +static int optee_ffa_from_msg_param(struct optee *optee, + struct tee_param *params, size_t num_params, + const struct optee_msg_param *msg_params) +{ + size_t n; + + for (n = 0; n < num_params; n++) { + struct tee_param *p = params + n; + const struct optee_msg_param *mp = msg_params + n; + u32 attr = mp->attr & OPTEE_MSG_ATTR_TYPE_MASK; + + switch (attr) { + case OPTEE_MSG_ATTR_TYPE_NONE: + p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + memset(&p->u, 0, sizeof(p->u)); + break; + case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT: + optee_from_msg_param_value(p, attr, mp); + break; + case OPTEE_MSG_ATTR_TYPE_FMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_FMEM_INOUT: + from_msg_param_ffa_mem(optee, p, attr, mp); + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static int to_msg_param_ffa_mem(struct optee_msg_param *mp, + const struct tee_param *p) +{ + struct tee_shm *shm = p->u.memref.shm; + + mp->attr = OPTEE_MSG_ATTR_TYPE_FMEM_INPUT + p->attr - + TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + + if (shm) { + u64 shm_offs = p->u.memref.shm_offs; + + mp->u.fmem.internal_offs = shm->offset; + + mp->u.fmem.offs_low = shm_offs; + mp->u.fmem.offs_high = shm_offs >> 32; + /* Check that the entire offset could be stored. */ + if (mp->u.fmem.offs_high != shm_offs >> 32) + return -EINVAL; + + mp->u.fmem.global_id = shm->sec_world_id; + } else { + memset(&mp->u, 0, sizeof(mp->u)); + mp->u.fmem.global_id = OPTEE_MSG_FMEM_INVALID_GLOBAL_ID; + } + mp->u.fmem.size = p->u.memref.size; + + return 0; +} + +/** + * optee_ffa_to_msg_param() - convert from struct tee_params to OPTEE_MSG + * parameters + * @optee: main service struct + * @msg_params: OPTEE_MSG parameters + * @num_params: number of elements in the parameter arrays + * @params: subsystem itnernal parameter representation + * Returns 0 on success or <0 on failure + */ +static int optee_ffa_to_msg_param(struct optee *optee, + struct optee_msg_param *msg_params, + size_t num_params, + const struct tee_param *params) +{ + size_t n; + + for (n = 0; n < num_params; n++) { + const struct tee_param *p = params + n; + struct optee_msg_param *mp = msg_params + n; + + switch (p->attr) { + case TEE_IOCTL_PARAM_ATTR_TYPE_NONE: + mp->attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + memset(&mp->u, 0, sizeof(mp->u)); + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT: + optee_to_msg_param_value(mp, p); + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: + if (to_msg_param_ffa_mem(mp, p)) + return -EINVAL; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +/* + * 3. Low level support functions to register shared memory in secure world + * + * Functions to register and unregister shared memory both for normal + * clients and for tee-supplicant. + */ + +static int optee_ffa_shm_register(struct tee_context *ctx, struct tee_shm *shm, + struct page **pages, size_t num_pages, + unsigned long start) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + const struct ffa_dev_ops *ffa_ops = optee->ffa.ffa_ops; + struct ffa_device *ffa_dev = optee->ffa.ffa_dev; + struct ffa_mem_region_attributes mem_attr = { + .receiver = ffa_dev->vm_id, + .attrs = FFA_MEM_RW, + }; + struct ffa_mem_ops_args args = { + .use_txbuf = true, + .attrs = &mem_attr, + .nattrs = 1, + }; + struct sg_table sgt; + int rc; + + rc = optee_check_mem_type(start, num_pages); + if (rc) + return rc; + + rc = sg_alloc_table_from_pages(&sgt, pages, num_pages, 0, + num_pages * PAGE_SIZE, GFP_KERNEL); + if (rc) + return rc; + args.sg = sgt.sgl; + rc = ffa_ops->memory_share(ffa_dev, &args); + sg_free_table(&sgt); + if (rc) + return rc; + + rc = optee_shm_add_ffa_handle(optee, shm, args.g_handle); + if (rc) { + ffa_ops->memory_reclaim(args.g_handle, 0); + return rc; + } + + shm->sec_world_id = args.g_handle; + + return 0; +} + +static int optee_ffa_shm_unregister(struct tee_context *ctx, + struct tee_shm *shm) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + const struct ffa_dev_ops *ffa_ops = optee->ffa.ffa_ops; + struct ffa_device *ffa_dev = optee->ffa.ffa_dev; + u64 global_handle = shm->sec_world_id; + struct ffa_send_direct_data data = { + .data0 = OPTEE_FFA_UNREGISTER_SHM, + .data1 = (u32)global_handle, + .data2 = (u32)(global_handle >> 32) + }; + int rc; + + optee_shm_rem_ffa_handle(optee, global_handle); + shm->sec_world_id = 0; + + rc = ffa_ops->sync_send_receive(ffa_dev, &data); + if (rc) + pr_err("Unregister SHM id 0x%llx rc %d\n", global_handle, rc); + + rc = ffa_ops->memory_reclaim(global_handle, 0); + if (rc) + pr_err("mem_reclaim: 0x%llx %d", global_handle, rc); + + return rc; +} + +static int optee_ffa_shm_unregister_supp(struct tee_context *ctx, + struct tee_shm *shm) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + const struct ffa_dev_ops *ffa_ops = optee->ffa.ffa_ops; + u64 global_handle = shm->sec_world_id; + int rc; + + /* + * We're skipping the OPTEE_FFA_YIELDING_CALL_UNREGISTER_SHM call + * since this is OP-TEE freeing via RPC so it has already retired + * this ID. + */ + + optee_shm_rem_ffa_handle(optee, global_handle); + rc = ffa_ops->memory_reclaim(global_handle, 0); + if (rc) + pr_err("mem_reclaim: 0x%llx %d", global_handle, rc); + + shm->sec_world_id = 0; + + return rc; +} + +/* + * 4. Dynamic shared memory pool based on alloc_pages() + * + * Implements an OP-TEE specific shared memory pool. + * The main function is optee_ffa_shm_pool_alloc_pages(). + */ + +static int pool_ffa_op_alloc(struct tee_shm_pool_mgr *poolm, + struct tee_shm *shm, size_t size) +{ + return optee_pool_op_alloc_helper(poolm, shm, size, + optee_ffa_shm_register); +} + +static void pool_ffa_op_free(struct tee_shm_pool_mgr *poolm, + struct tee_shm *shm) +{ + optee_ffa_shm_unregister(shm->ctx, shm); + free_pages((unsigned long)shm->kaddr, get_order(shm->size)); + shm->kaddr = NULL; +} + +static void pool_ffa_op_destroy_poolmgr(struct tee_shm_pool_mgr *poolm) +{ + kfree(poolm); +} + +static const struct tee_shm_pool_mgr_ops pool_ffa_ops = { + .alloc = pool_ffa_op_alloc, + .free = pool_ffa_op_free, + .destroy_poolmgr = pool_ffa_op_destroy_poolmgr, +}; + +/** + * optee_ffa_shm_pool_alloc_pages() - create page-based allocator pool + * + * This pool is used with OP-TEE over FF-A. In this case command buffers + * and such are allocated from kernel's own memory. + */ +static struct tee_shm_pool_mgr *optee_ffa_shm_pool_alloc_pages(void) +{ + struct tee_shm_pool_mgr *mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); + + if (!mgr) + return ERR_PTR(-ENOMEM); + + mgr->ops = &pool_ffa_ops; + + return mgr; +} + +/* + * 5. Do a normal scheduled call into secure world + * + * The function optee_ffa_do_call_with_arg() performs a normal scheduled + * call into secure world. During this call may normal world request help + * from normal world using RPCs, Remote Procedure Calls. This includes + * delivery of non-secure interrupts to for instance allow rescheduling of + * the current task. + */ + +static void handle_ffa_rpc_func_cmd_shm_alloc(struct tee_context *ctx, + struct optee_msg_arg *arg) +{ + struct tee_shm *shm; + + if (arg->num_params != 1 || + arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_INPUT) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + switch (arg->params[0].u.value.a) { + case OPTEE_RPC_SHM_TYPE_APPL: + shm = optee_rpc_cmd_alloc_suppl(ctx, arg->params[0].u.value.b); + break; + case OPTEE_RPC_SHM_TYPE_KERNEL: + shm = tee_shm_alloc(ctx, arg->params[0].u.value.b, + TEE_SHM_MAPPED | TEE_SHM_PRIV); + break; + default: + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + if (IS_ERR(shm)) { + arg->ret = TEEC_ERROR_OUT_OF_MEMORY; + return; + } + + arg->params[0] = (struct optee_msg_param){ + .attr = OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT, + .u.fmem.size = tee_shm_get_size(shm), + .u.fmem.global_id = shm->sec_world_id, + .u.fmem.internal_offs = shm->offset, + }; + + arg->ret = TEEC_SUCCESS; +} + +static void handle_ffa_rpc_func_cmd_shm_free(struct tee_context *ctx, + struct optee *optee, + struct optee_msg_arg *arg) +{ + struct tee_shm *shm; + + if (arg->num_params != 1 || + arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_INPUT) + goto err_bad_param; + + shm = optee_shm_from_ffa_handle(optee, arg->params[0].u.value.b); + if (!shm) + goto err_bad_param; + switch (arg->params[0].u.value.a) { + case OPTEE_RPC_SHM_TYPE_APPL: + optee_rpc_cmd_free_suppl(ctx, shm); + break; + case OPTEE_RPC_SHM_TYPE_KERNEL: + tee_shm_free(shm); + break; + default: + goto err_bad_param; + } + arg->ret = TEEC_SUCCESS; + return; + +err_bad_param: + arg->ret = TEEC_ERROR_BAD_PARAMETERS; +} + +static void handle_ffa_rpc_func_cmd(struct tee_context *ctx, + struct optee_msg_arg *arg) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + + arg->ret_origin = TEEC_ORIGIN_COMMS; + switch (arg->cmd) { + case OPTEE_RPC_CMD_SHM_ALLOC: + handle_ffa_rpc_func_cmd_shm_alloc(ctx, arg); + break; + case OPTEE_RPC_CMD_SHM_FREE: + handle_ffa_rpc_func_cmd_shm_free(ctx, optee, arg); + break; + default: + optee_rpc_cmd(ctx, optee, arg); + } +} + +static void optee_handle_ffa_rpc(struct tee_context *ctx, u32 cmd, + struct optee_msg_arg *arg) +{ + switch (cmd) { + case OPTEE_FFA_YIELDING_CALL_RETURN_RPC_CMD: + handle_ffa_rpc_func_cmd(ctx, arg); + break; + case OPTEE_FFA_YIELDING_CALL_RETURN_INTERRUPT: + /* Interrupt delivered by now */ + break; + default: + pr_warn("Unknown RPC func 0x%x\n", cmd); + break; + } +} + +static int optee_ffa_yielding_call(struct tee_context *ctx, + struct ffa_send_direct_data *data, + struct optee_msg_arg *rpc_arg) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + const struct ffa_dev_ops *ffa_ops = optee->ffa.ffa_ops; + struct ffa_device *ffa_dev = optee->ffa.ffa_dev; + struct optee_call_waiter w; + u32 cmd = data->data0; + u32 w4 = data->data1; + u32 w5 = data->data2; + u32 w6 = data->data3; + int rc; + + /* Initialize waiter */ + optee_cq_wait_init(&optee->call_queue, &w); + while (true) { + rc = ffa_ops->sync_send_receive(ffa_dev, data); + if (rc) + goto done; + + switch ((int)data->data0) { + case TEEC_SUCCESS: + break; + case TEEC_ERROR_BUSY: + if (cmd == OPTEE_FFA_YIELDING_CALL_RESUME) { + rc = -EIO; + goto done; + } + + /* + * Out of threads in secure world, wait for a thread + * become available. + */ + optee_cq_wait_for_completion(&optee->call_queue, &w); + data->data0 = cmd; + data->data1 = w4; + data->data2 = w5; + data->data3 = w6; + continue; + default: + rc = -EIO; + goto done; + } + + if (data->data1 == OPTEE_FFA_YIELDING_CALL_RETURN_DONE) + goto done; + + /* + * OP-TEE has returned with a RPC request. + * + * Note that data->data4 (passed in register w7) is already + * filled in by ffa_ops->sync_send_receive() returning + * above. + */ + cond_resched(); + optee_handle_ffa_rpc(ctx, data->data1, rpc_arg); + cmd = OPTEE_FFA_YIELDING_CALL_RESUME; + data->data0 = cmd; + data->data1 = 0; + data->data2 = 0; + data->data3 = 0; + } +done: + /* + * We're done with our thread in secure world, if there's any + * thread waiters wake up one. + */ + optee_cq_wait_final(&optee->call_queue, &w); + + return rc; +} + +/** + * optee_ffa_do_call_with_arg() - Do a FF-A call to enter OP-TEE in secure world + * @ctx: calling context + * @shm: shared memory holding the message to pass to secure world + * + * Does a FF-A call to OP-TEE in secure world and handles eventual resulting + * Remote Procedure Calls (RPC) from OP-TEE. + * + * Returns return code from FF-A, 0 is OK + */ + +static int optee_ffa_do_call_with_arg(struct tee_context *ctx, + struct tee_shm *shm) +{ + struct ffa_send_direct_data data = { + .data0 = OPTEE_FFA_YIELDING_CALL_WITH_ARG, + .data1 = (u32)shm->sec_world_id, + .data2 = (u32)(shm->sec_world_id >> 32), + .data3 = shm->offset, + }; + struct optee_msg_arg *arg = tee_shm_get_va(shm, 0); + unsigned int rpc_arg_offs = OPTEE_MSG_GET_ARG_SIZE(arg->num_params); + struct optee_msg_arg *rpc_arg = tee_shm_get_va(shm, rpc_arg_offs); + + return optee_ffa_yielding_call(ctx, &data, rpc_arg); +} + +/* + * 6. Driver initialization + * + * During driver inititialization is the OP-TEE Secure Partition is probed + * to find out which features it supports so the driver can be initialized + * with a matching configuration. + */ + +static bool optee_ffa_api_is_compatbile(struct ffa_device *ffa_dev, + const struct ffa_dev_ops *ops) +{ + struct ffa_send_direct_data data = { OPTEE_FFA_GET_API_VERSION }; + int rc; + + ops->mode_32bit_set(ffa_dev); + + rc = ops->sync_send_receive(ffa_dev, &data); + if (rc) { + pr_err("Unexpected error %d\n", rc); + return false; + } + if (data.data0 != OPTEE_FFA_VERSION_MAJOR || + data.data1 < OPTEE_FFA_VERSION_MINOR) { + pr_err("Incompatible OP-TEE API version %lu.%lu", + data.data0, data.data1); + return false; + } + + data = (struct ffa_send_direct_data){ OPTEE_FFA_GET_OS_VERSION }; + rc = ops->sync_send_receive(ffa_dev, &data); + if (rc) { + pr_err("Unexpected error %d\n", rc); + return false; + } + if (data.data2) + pr_info("revision %lu.%lu (%08lx)", + data.data0, data.data1, data.data2); + else + pr_info("revision %lu.%lu", data.data0, data.data1); + + return true; +} + +static bool optee_ffa_exchange_caps(struct ffa_device *ffa_dev, + const struct ffa_dev_ops *ops, + unsigned int *rpc_arg_count) +{ + struct ffa_send_direct_data data = { OPTEE_FFA_EXCHANGE_CAPABILITIES }; + int rc; + + rc = ops->sync_send_receive(ffa_dev, &data); + if (rc) { + pr_err("Unexpected error %d", rc); + return false; + } + if (data.data0) { + pr_err("Unexpected exchange error %lu", data.data0); + return false; + } + + *rpc_arg_count = (u8)data.data1; + + return true; +} + +static struct tee_shm_pool *optee_ffa_config_dyn_shm(void) +{ + struct tee_shm_pool_mgr *priv_mgr; + struct tee_shm_pool_mgr *dmabuf_mgr; + void *rc; + + rc = optee_ffa_shm_pool_alloc_pages(); + if (IS_ERR(rc)) + return rc; + priv_mgr = rc; + + rc = optee_ffa_shm_pool_alloc_pages(); + if (IS_ERR(rc)) { + tee_shm_pool_mgr_destroy(priv_mgr); + return rc; + } + dmabuf_mgr = rc; + + rc = tee_shm_pool_alloc(priv_mgr, dmabuf_mgr); + if (IS_ERR(rc)) { + tee_shm_pool_mgr_destroy(priv_mgr); + tee_shm_pool_mgr_destroy(dmabuf_mgr); + } + + return rc; +} + +static void optee_ffa_get_version(struct tee_device *teedev, + struct tee_ioctl_version_data *vers) +{ + struct tee_ioctl_version_data v = { + .impl_id = TEE_IMPL_ID_OPTEE, + .impl_caps = TEE_OPTEE_CAP_TZ, + .gen_caps = TEE_GEN_CAP_GP | TEE_GEN_CAP_REG_MEM | + TEE_GEN_CAP_MEMREF_NULL, + }; + + *vers = v; +} + +static int optee_ffa_open(struct tee_context *ctx) +{ + return optee_open(ctx, true); +} + +static const struct tee_driver_ops optee_ffa_clnt_ops = { + .get_version = optee_ffa_get_version, + .open = optee_ffa_open, + .release = optee_release, + .open_session = optee_open_session, + .close_session = optee_close_session, + .invoke_func = optee_invoke_func, + .cancel_req = optee_cancel_req, + .shm_register = optee_ffa_shm_register, + .shm_unregister = optee_ffa_shm_unregister, +}; + +static const struct tee_desc optee_ffa_clnt_desc = { + .name = DRIVER_NAME "-ffa-clnt", + .ops = &optee_ffa_clnt_ops, + .owner = THIS_MODULE, +}; + +static const struct tee_driver_ops optee_ffa_supp_ops = { + .get_version = optee_ffa_get_version, + .open = optee_ffa_open, + .release = optee_release_supp, + .supp_recv = optee_supp_recv, + .supp_send = optee_supp_send, + .shm_register = optee_ffa_shm_register, /* same as for clnt ops */ + .shm_unregister = optee_ffa_shm_unregister_supp, +}; + +static const struct tee_desc optee_ffa_supp_desc = { + .name = DRIVER_NAME "-ffa-supp", + .ops = &optee_ffa_supp_ops, + .owner = THIS_MODULE, + .flags = TEE_DESC_PRIVILEGED, +}; + +static const struct optee_ops optee_ffa_ops = { + .do_call_with_arg = optee_ffa_do_call_with_arg, + .to_msg_param = optee_ffa_to_msg_param, + .from_msg_param = optee_ffa_from_msg_param, +}; + +static void optee_ffa_remove(struct ffa_device *ffa_dev) +{ + struct optee *optee = ffa_dev->dev.driver_data; + + optee_remove_common(optee); + + mutex_destroy(&optee->ffa.mutex); + rhashtable_free_and_destroy(&optee->ffa.global_ids, rh_free_fn, NULL); + + kfree(optee); +} + +static int optee_ffa_probe(struct ffa_device *ffa_dev) +{ + const struct ffa_dev_ops *ffa_ops; + unsigned int rpc_arg_count; + struct tee_device *teedev; + struct optee *optee; + int rc; + + ffa_ops = ffa_dev_ops_get(ffa_dev); + if (!ffa_ops) { + pr_warn("failed \"method\" init: ffa\n"); + return -ENOENT; + } + + if (!optee_ffa_api_is_compatbile(ffa_dev, ffa_ops)) + return -EINVAL; + + if (!optee_ffa_exchange_caps(ffa_dev, ffa_ops, &rpc_arg_count)) + return -EINVAL; + + optee = kzalloc(sizeof(*optee), GFP_KERNEL); + if (!optee) { + rc = -ENOMEM; + goto err; + } + optee->pool = optee_ffa_config_dyn_shm(); + if (IS_ERR(optee->pool)) { + rc = PTR_ERR(optee->pool); + optee->pool = NULL; + goto err; + } + + optee->ops = &optee_ffa_ops; + optee->ffa.ffa_dev = ffa_dev; + optee->ffa.ffa_ops = ffa_ops; + optee->rpc_arg_count = rpc_arg_count; + + teedev = tee_device_alloc(&optee_ffa_clnt_desc, NULL, optee->pool, + optee); + if (IS_ERR(teedev)) { + rc = PTR_ERR(teedev); + goto err; + } + optee->teedev = teedev; + + teedev = tee_device_alloc(&optee_ffa_supp_desc, NULL, optee->pool, + optee); + if (IS_ERR(teedev)) { + rc = PTR_ERR(teedev); + goto err; + } + optee->supp_teedev = teedev; + + rc = tee_device_register(optee->teedev); + if (rc) + goto err; + + rc = tee_device_register(optee->supp_teedev); + if (rc) + goto err; + + rc = rhashtable_init(&optee->ffa.global_ids, &shm_rhash_params); + if (rc) + goto err; + mutex_init(&optee->ffa.mutex); + mutex_init(&optee->call_queue.mutex); + INIT_LIST_HEAD(&optee->call_queue.waiters); + optee_wait_queue_init(&optee->wait_queue); + optee_supp_init(&optee->supp); + ffa_dev_set_drvdata(ffa_dev, optee); + + rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); + if (rc) { + optee_ffa_remove(ffa_dev); + return rc; + } + + pr_info("initialized driver\n"); + return 0; +err: + /* + * tee_device_unregister() is safe to call even if the + * devices hasn't been registered with + * tee_device_register() yet. + */ + tee_device_unregister(optee->supp_teedev); + tee_device_unregister(optee->teedev); + if (optee->pool) + tee_shm_pool_free(optee->pool); + kfree(optee); + return rc; +} + +static const struct ffa_device_id optee_ffa_device_id[] = { + /* 486178e0-e7f8-11e3-bc5e0002a5d5c51b */ + { UUID_INIT(0x486178e0, 0xe7f8, 0x11e3, + 0xbc, 0x5e, 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b) }, + {} +}; + +static struct ffa_driver optee_ffa_driver = { + .name = "optee", + .probe = optee_ffa_probe, + .remove = optee_ffa_remove, + .id_table = optee_ffa_device_id, +}; + +int optee_ffa_abi_register(void) +{ + if (IS_REACHABLE(CONFIG_ARM_FFA_TRANSPORT)) + return ffa_register(&optee_ffa_driver); + else + return -EOPNOTSUPP; +} + +void optee_ffa_abi_unregister(void) +{ + if (IS_REACHABLE(CONFIG_ARM_FFA_TRANSPORT)) + ffa_unregister(&optee_ffa_driver); +} diff --git a/drivers/tee/optee/optee_ffa.h b/drivers/tee/optee/optee_ffa.h new file mode 100644 index 000000000000..ee3a03fc392c --- /dev/null +++ b/drivers/tee/optee/optee_ffa.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2019-2021, Linaro Limited + */ + +/* + * This file is exported by OP-TEE and is kept in sync between secure world + * and normal world drivers. We're using ARM FF-A 1.0 specification. + */ + +#ifndef __OPTEE_FFA_H +#define __OPTEE_FFA_H + +#include <linux/arm_ffa.h> + +/* + * Normal world sends requests with FFA_MSG_SEND_DIRECT_REQ and + * responses are returned with FFA_MSG_SEND_DIRECT_RESP for normal + * messages. + * + * All requests with FFA_MSG_SEND_DIRECT_REQ and FFA_MSG_SEND_DIRECT_RESP + * are using the AArch32 SMC calling convention with register usage as + * defined in FF-A specification: + * w0: Function ID (0x8400006F or 0x84000070) + * w1: Source/Destination IDs + * w2: Reserved (MBZ) + * w3-w7: Implementation defined, free to be used below + */ + +#define OPTEE_FFA_VERSION_MAJOR 1 +#define OPTEE_FFA_VERSION_MINOR 0 + +#define OPTEE_FFA_BLOCKING_CALL(id) (id) +#define OPTEE_FFA_YIELDING_CALL_BIT 31 +#define OPTEE_FFA_YIELDING_CALL(id) ((id) | BIT(OPTEE_FFA_YIELDING_CALL_BIT)) + +/* + * Returns the API version implemented, currently follows the FF-A version. + * Call register usage: + * w3: Service ID, OPTEE_FFA_GET_API_VERSION + * w4-w7: Not used (MBZ) + * + * Return register usage: + * w3: OPTEE_FFA_VERSION_MAJOR + * w4: OPTEE_FFA_VERSION_MINOR + * w5-w7: Not used (MBZ) + */ +#define OPTEE_FFA_GET_API_VERSION OPTEE_FFA_BLOCKING_CALL(0) + +/* + * Returns the revision of OP-TEE. + * + * Used by non-secure world to figure out which version of the Trusted OS + * is installed. Note that the returned revision is the revision of the + * Trusted OS, not of the API. + * + * Call register usage: + * w3: Service ID, OPTEE_FFA_GET_OS_VERSION + * w4-w7: Unused (MBZ) + * + * Return register usage: + * w3: CFG_OPTEE_REVISION_MAJOR + * w4: CFG_OPTEE_REVISION_MINOR + * w5: TEE_IMPL_GIT_SHA1 (or zero if not supported) + */ +#define OPTEE_FFA_GET_OS_VERSION OPTEE_FFA_BLOCKING_CALL(1) + +/* + * Exchange capabilities between normal world and secure world. + * + * Currently there are no defined capabilities. When features are added new + * capabilities may be added. + * + * Call register usage: + * w3: Service ID, OPTEE_FFA_EXCHANGE_CAPABILITIES + * w4-w7: Note used (MBZ) + * + * Return register usage: + * w3: Error code, 0 on success + * w4: Bit[7:0]: Number of parameters needed for RPC to be supplied + * as the second MSG arg struct for + * OPTEE_FFA_YIELDING_CALL_WITH_ARG. + * Bit[31:8]: Reserved (MBZ) + * w5-w7: Note used (MBZ) + */ +#define OPTEE_FFA_EXCHANGE_CAPABILITIES OPTEE_FFA_BLOCKING_CALL(2) + +/* + * Unregister shared memory + * + * Call register usage: + * w3: Service ID, OPTEE_FFA_YIELDING_CALL_UNREGISTER_SHM + * w4: Shared memory handle, lower bits + * w5: Shared memory handle, higher bits + * w6-w7: Not used (MBZ) + * + * Return register usage: + * w3: Error code, 0 on success + * w4-w7: Note used (MBZ) + */ +#define OPTEE_FFA_UNREGISTER_SHM OPTEE_FFA_BLOCKING_CALL(3) + +/* + * Call with struct optee_msg_arg as argument in the supplied shared memory + * with a zero internal offset and normal cached memory attributes. + * Register usage: + * w3: Service ID, OPTEE_FFA_YIELDING_CALL_WITH_ARG + * w4: Lower 32 bits of a 64-bit Shared memory handle + * w5: Upper 32 bits of a 64-bit Shared memory handle + * w6: Offset into shared memory pointing to a struct optee_msg_arg + * right after the parameters of this struct (at offset + * OPTEE_MSG_GET_ARG_SIZE(num_params) follows a struct optee_msg_arg + * for RPC, this struct has reserved space for the number of RPC + * parameters as returned by OPTEE_FFA_EXCHANGE_CAPABILITIES. + * w7: Not used (MBZ) + * Resume from RPC. Register usage: + * w3: Service ID, OPTEE_FFA_YIELDING_CALL_RESUME + * w4-w6: Not used (MBZ) + * w7: Resume info + * + * Normal return (yielding call is completed). Register usage: + * w3: Error code, 0 on success + * w4: OPTEE_FFA_YIELDING_CALL_RETURN_DONE + * w5-w7: Not used (MBZ) + * + * RPC interrupt return (RPC from secure world). Register usage: + * w3: Error code == 0 + * w4: Any defined RPC code but OPTEE_FFA_YIELDING_CALL_RETURN_DONE + * w5-w6: Not used (MBZ) + * w7: Resume info + * + * Possible error codes in register w3: + * 0: Success + * FFA_DENIED: w4 isn't one of OPTEE_FFA_YIELDING_CALL_START + * OPTEE_FFA_YIELDING_CALL_RESUME + * + * Possible error codes for OPTEE_FFA_YIELDING_CALL_START, + * FFA_BUSY: Number of OP-TEE OS threads exceeded, + * try again later + * FFA_DENIED: RPC shared memory object not found + * FFA_INVALID_PARAMETER: Bad shared memory handle or offset into the memory + * + * Possible error codes for OPTEE_FFA_YIELDING_CALL_RESUME + * FFA_INVALID_PARAMETER: Bad resume info + */ +#define OPTEE_FFA_YIELDING_CALL_WITH_ARG OPTEE_FFA_YIELDING_CALL(0) +#define OPTEE_FFA_YIELDING_CALL_RESUME OPTEE_FFA_YIELDING_CALL(1) + +#define OPTEE_FFA_YIELDING_CALL_RETURN_DONE 0 +#define OPTEE_FFA_YIELDING_CALL_RETURN_RPC_CMD 1 +#define OPTEE_FFA_YIELDING_CALL_RETURN_INTERRUPT 2 + +#endif /*__OPTEE_FFA_H*/ diff --git a/drivers/tee/optee/optee_msg.h b/drivers/tee/optee/optee_msg.h index e3d72d09c484..2422e185d400 100644 --- a/drivers/tee/optee/optee_msg.h +++ b/drivers/tee/optee/optee_msg.h @@ -28,6 +28,9 @@ #define OPTEE_MSG_ATTR_TYPE_RMEM_INPUT 0x5 #define OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT 0x6 #define OPTEE_MSG_ATTR_TYPE_RMEM_INOUT 0x7 +#define OPTEE_MSG_ATTR_TYPE_FMEM_INPUT OPTEE_MSG_ATTR_TYPE_RMEM_INPUT +#define OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT +#define OPTEE_MSG_ATTR_TYPE_FMEM_INOUT OPTEE_MSG_ATTR_TYPE_RMEM_INOUT #define OPTEE_MSG_ATTR_TYPE_TMEM_INPUT 0x9 #define OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT 0xa #define OPTEE_MSG_ATTR_TYPE_TMEM_INOUT 0xb @@ -96,6 +99,8 @@ */ #define OPTEE_MSG_NONCONTIG_PAGE_SIZE 4096 +#define OPTEE_MSG_FMEM_INVALID_GLOBAL_ID 0xffffffffffffffff + /** * struct optee_msg_param_tmem - temporary memory reference parameter * @buf_ptr: Address of the buffer @@ -128,6 +133,23 @@ struct optee_msg_param_rmem { }; /** + * struct optee_msg_param_fmem - ffa memory reference parameter + * @offs_lower: Lower bits of offset into shared memory reference + * @offs_upper: Upper bits of offset into shared memory reference + * @internal_offs: Internal offset into the first page of shared memory + * reference + * @size: Size of the buffer + * @global_id: Global identifier of Shared memory + */ +struct optee_msg_param_fmem { + u32 offs_low; + u16 offs_high; + u16 internal_offs; + u64 size; + u64 global_id; +}; + +/** * struct optee_msg_param_value - opaque value parameter * * Value parameters are passed unchecked between normal and secure world. @@ -143,13 +165,15 @@ struct optee_msg_param_value { * @attr: attributes * @tmem: parameter by temporary memory reference * @rmem: parameter by registered memory reference + * @fmem: parameter by ffa registered memory reference * @value: parameter by opaque value * @octets: parameter by octet string * * @attr & OPTEE_MSG_ATTR_TYPE_MASK indicates if tmem, rmem or value is used in * the union. OPTEE_MSG_ATTR_TYPE_VALUE_* indicates value or octets, * OPTEE_MSG_ATTR_TYPE_TMEM_* indicates @tmem and - * OPTEE_MSG_ATTR_TYPE_RMEM_* indicates @rmem, + * OPTEE_MSG_ATTR_TYPE_RMEM_* or the alias PTEE_MSG_ATTR_TYPE_FMEM_* indicates + * @rmem or @fmem depending on the conduit. * OPTEE_MSG_ATTR_TYPE_NONE indicates that none of the members are used. */ struct optee_msg_param { @@ -157,6 +181,7 @@ struct optee_msg_param { union { struct optee_msg_param_tmem tmem; struct optee_msg_param_rmem rmem; + struct optee_msg_param_fmem fmem; struct optee_msg_param_value value; u8 octets[24]; } u; diff --git a/drivers/tee/optee/optee_private.h b/drivers/tee/optee/optee_private.h index f6bb4a763ba9..6660e05298db 100644 --- a/drivers/tee/optee/optee_private.h +++ b/drivers/tee/optee/optee_private.h @@ -1,17 +1,20 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2015, Linaro Limited + * Copyright (c) 2015-2021, Linaro Limited */ #ifndef OPTEE_PRIVATE_H #define OPTEE_PRIVATE_H #include <linux/arm-smccc.h> +#include <linux/rhashtable.h> #include <linux/semaphore.h> #include <linux/tee_drv.h> #include <linux/types.h> #include "optee_msg.h" +#define DRIVER_NAME "optee" + #define OPTEE_MAX_ARG_SIZE 1024 /* Some Global Platform error codes used in this driver */ @@ -20,6 +23,7 @@ #define TEEC_ERROR_NOT_SUPPORTED 0xFFFF000A #define TEEC_ERROR_COMMUNICATION 0xFFFF000E #define TEEC_ERROR_OUT_OF_MEMORY 0xFFFF000C +#define TEEC_ERROR_BUSY 0xFFFF000D #define TEEC_ERROR_SHORT_BUFFER 0xFFFF0010 #define TEEC_ORIGIN_COMMS 0x00000002 @@ -29,6 +33,11 @@ typedef void (optee_invoke_fn)(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, struct arm_smccc_res *); +struct optee_call_waiter { + struct list_head list_node; + struct completion c; +}; + struct optee_call_queue { /* Serializes access to this struct */ struct mutex mutex; @@ -66,19 +75,65 @@ struct optee_supp { struct completion reqs_c; }; +struct optee_smc { + optee_invoke_fn *invoke_fn; + void *memremaped_shm; + u32 sec_caps; +}; + +/** + * struct optee_ffa_data - FFA communication struct + * @ffa_dev FFA device, contains the destination id, the id of + * OP-TEE in secure world + * @ffa_ops FFA operations + * @mutex Serializes access to @global_ids + * @global_ids FF-A shared memory global handle translation + */ +struct optee_ffa { + struct ffa_device *ffa_dev; + const struct ffa_dev_ops *ffa_ops; + /* Serializes access to @global_ids */ + struct mutex mutex; + struct rhashtable global_ids; +}; + +struct optee; + +/** + * struct optee_ops - OP-TEE driver internal operations + * @do_call_with_arg: enters OP-TEE in secure world + * @to_msg_param: converts from struct tee_param to OPTEE_MSG parameters + * @from_msg_param: converts from OPTEE_MSG parameters to struct tee_param + * + * These OPs are only supposed to be used internally in the OP-TEE driver + * as a way of abstracting the different methogs of entering OP-TEE in + * secure world. + */ +struct optee_ops { + int (*do_call_with_arg)(struct tee_context *ctx, + struct tee_shm *shm_arg); + int (*to_msg_param)(struct optee *optee, + struct optee_msg_param *msg_params, + size_t num_params, const struct tee_param *params); + int (*from_msg_param)(struct optee *optee, struct tee_param *params, + size_t num_params, + const struct optee_msg_param *msg_params); +}; + /** * struct optee - main service struct * @supp_teedev: supplicant device + * @ops: internal callbacks for different ways to reach secure + * world * @teedev: client device - * @invoke_fn: function to issue smc or hvc + * @smc: specific to SMC ABI + * @ffa: specific to FF-A ABI * @call_queue: queue of threads waiting to call @invoke_fn * @wait_queue: queue of threads from secure world waiting for a * secure world sync object * @supp: supplicant synchronization struct for RPC to supplicant * @pool: shared memory pool - * @memremaped_shm virtual address of memory in shared memory pool - * @sec_caps: secure world capabilities defined by - * OPTEE_SMC_SEC_CAP_* in optee_smc.h + * @rpc_arg_count: If > 0 number of RPC parameters to make room for * @scan_bus_done flag if device registation was already done. * @scan_bus_wq workqueue to scan optee bus and register optee drivers * @scan_bus_work workq to scan optee bus and register optee drivers @@ -86,13 +141,16 @@ struct optee_supp { struct optee { struct tee_device *supp_teedev; struct tee_device *teedev; - optee_invoke_fn *invoke_fn; + const struct optee_ops *ops; + union { + struct optee_smc smc; + struct optee_ffa ffa; + }; struct optee_call_queue call_queue; struct optee_wait_queue wait_queue; struct optee_supp supp; struct tee_shm_pool *pool; - void *memremaped_shm; - u32 sec_caps; + unsigned int rpc_arg_count; bool scan_bus_done; struct workqueue_struct *scan_bus_wq; struct work_struct scan_bus_work; @@ -127,10 +185,6 @@ struct optee_call_ctx { size_t num_entries; }; -void optee_handle_rpc(struct tee_context *ctx, struct optee_rpc_param *param, - struct optee_call_ctx *call_ctx); -void optee_rpc_finalize_call(struct optee_call_ctx *call_ctx); - void optee_wait_queue_init(struct optee_wait_queue *wq); void optee_wait_queue_exit(struct optee_wait_queue *wq); @@ -148,43 +202,68 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params, int optee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params, struct tee_param *param); -u32 optee_do_call_with_arg(struct tee_context *ctx, phys_addr_t parg); int optee_open_session(struct tee_context *ctx, struct tee_ioctl_open_session_arg *arg, struct tee_param *param); +int optee_close_session_helper(struct tee_context *ctx, u32 session); int optee_close_session(struct tee_context *ctx, u32 session); int optee_invoke_func(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg, struct tee_param *param); int optee_cancel_req(struct tee_context *ctx, u32 cancel_id, u32 session); -void optee_enable_shm_cache(struct optee *optee); -void optee_disable_shm_cache(struct optee *optee); -void optee_disable_unmapped_shm_cache(struct optee *optee); +#define PTA_CMD_GET_DEVICES 0x0 +#define PTA_CMD_GET_DEVICES_SUPP 0x1 +int optee_enumerate_devices(u32 func); +void optee_unregister_devices(void); -int optee_shm_register(struct tee_context *ctx, struct tee_shm *shm, - struct page **pages, size_t num_pages, - unsigned long start); -int optee_shm_unregister(struct tee_context *ctx, struct tee_shm *shm); +int optee_pool_op_alloc_helper(struct tee_shm_pool_mgr *poolm, + struct tee_shm *shm, size_t size, + int (*shm_register)(struct tee_context *ctx, + struct tee_shm *shm, + struct page **pages, + size_t num_pages, + unsigned long start)); -int optee_shm_register_supp(struct tee_context *ctx, struct tee_shm *shm, - struct page **pages, size_t num_pages, - unsigned long start); -int optee_shm_unregister_supp(struct tee_context *ctx, struct tee_shm *shm); -int optee_from_msg_param(struct tee_param *params, size_t num_params, - const struct optee_msg_param *msg_params); -int optee_to_msg_param(struct optee_msg_param *msg_params, size_t num_params, - const struct tee_param *params); +void optee_remove_common(struct optee *optee); +int optee_open(struct tee_context *ctx, bool cap_memref_null); +void optee_release(struct tee_context *ctx); +void optee_release_supp(struct tee_context *ctx); -u64 *optee_allocate_pages_list(size_t num_entries); -void optee_free_pages_list(void *array, size_t num_entries); -void optee_fill_pages_list(u64 *dst, struct page **pages, int num_pages, - size_t page_offset); +static inline void optee_from_msg_param_value(struct tee_param *p, u32 attr, + const struct optee_msg_param *mp) +{ + p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT + + attr - OPTEE_MSG_ATTR_TYPE_VALUE_INPUT; + p->u.value.a = mp->u.value.a; + p->u.value.b = mp->u.value.b; + p->u.value.c = mp->u.value.c; +} -#define PTA_CMD_GET_DEVICES 0x0 -#define PTA_CMD_GET_DEVICES_SUPP 0x1 -int optee_enumerate_devices(u32 func); -void optee_unregister_devices(void); +static inline void optee_to_msg_param_value(struct optee_msg_param *mp, + const struct tee_param *p) +{ + mp->attr = OPTEE_MSG_ATTR_TYPE_VALUE_INPUT + p->attr - + TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + mp->u.value.a = p->u.value.a; + mp->u.value.b = p->u.value.b; + mp->u.value.c = p->u.value.c; +} + +void optee_cq_wait_init(struct optee_call_queue *cq, + struct optee_call_waiter *w); +void optee_cq_wait_for_completion(struct optee_call_queue *cq, + struct optee_call_waiter *w); +void optee_cq_wait_final(struct optee_call_queue *cq, + struct optee_call_waiter *w); +int optee_check_mem_type(unsigned long start, size_t num_pages); +struct tee_shm *optee_get_msg_arg(struct tee_context *ctx, size_t num_params, + struct optee_msg_arg **msg_arg); + +struct tee_shm *optee_rpc_cmd_alloc_suppl(struct tee_context *ctx, size_t sz); +void optee_rpc_cmd_free_suppl(struct tee_context *ctx, struct tee_shm *shm); +void optee_rpc_cmd(struct tee_context *ctx, struct optee *optee, + struct optee_msg_arg *arg); /* * Small helpers @@ -201,4 +280,10 @@ static inline void reg_pair_from_64(u32 *reg0, u32 *reg1, u64 val) *reg1 = val; } +/* Registration of the ABIs */ +int optee_smc_abi_register(void); +void optee_smc_abi_unregister(void); +int optee_ffa_abi_register(void); +void optee_ffa_abi_unregister(void); + #endif /*OPTEE_PRIVATE_H*/ diff --git a/drivers/tee/optee/rpc.c b/drivers/tee/optee/rpc.c index efbaff7ad7e5..cd642e340eaf 100644 --- a/drivers/tee/optee/rpc.c +++ b/drivers/tee/optee/rpc.c @@ -1,17 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2015-2016, Linaro Limited + * Copyright (c) 2015-2021, Linaro Limited */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/delay.h> -#include <linux/device.h> #include <linux/i2c.h> #include <linux/slab.h> #include <linux/tee_drv.h> #include "optee_private.h" -#include "optee_smc.h" #include "optee_rpc_cmd.h" struct wq_entry { @@ -55,6 +53,7 @@ bad: static void handle_rpc_func_cmd_i2c_transfer(struct tee_context *ctx, struct optee_msg_arg *arg) { + struct optee *optee = tee_get_drvdata(ctx->teedev); struct tee_param *params; struct i2c_adapter *adapter; struct i2c_msg msg = { }; @@ -79,7 +78,8 @@ static void handle_rpc_func_cmd_i2c_transfer(struct tee_context *ctx, return; } - if (optee_from_msg_param(params, arg->num_params, arg->params)) + if (optee->ops->from_msg_param(optee, params, arg->num_params, + arg->params)) goto bad; for (i = 0; i < arg->num_params; i++) { @@ -122,7 +122,8 @@ static void handle_rpc_func_cmd_i2c_transfer(struct tee_context *ctx, arg->ret = TEEC_ERROR_COMMUNICATION; } else { params[3].u.value.a = msg.len; - if (optee_to_msg_param(arg->params, arg->num_params, params)) + if (optee->ops->to_msg_param(optee, arg->params, + arg->num_params, params)) arg->ret = TEEC_ERROR_BAD_PARAMETERS; else arg->ret = TEEC_SUCCESS; @@ -234,7 +235,7 @@ bad: arg->ret = TEEC_ERROR_BAD_PARAMETERS; } -static void handle_rpc_supp_cmd(struct tee_context *ctx, +static void handle_rpc_supp_cmd(struct tee_context *ctx, struct optee *optee, struct optee_msg_arg *arg) { struct tee_param *params; @@ -248,20 +249,22 @@ static void handle_rpc_supp_cmd(struct tee_context *ctx, return; } - if (optee_from_msg_param(params, arg->num_params, arg->params)) { + if (optee->ops->from_msg_param(optee, params, arg->num_params, + arg->params)) { arg->ret = TEEC_ERROR_BAD_PARAMETERS; goto out; } arg->ret = optee_supp_thrd_req(ctx, arg->cmd, arg->num_params, params); - if (optee_to_msg_param(arg->params, arg->num_params, params)) + if (optee->ops->to_msg_param(optee, arg->params, arg->num_params, + params)) arg->ret = TEEC_ERROR_BAD_PARAMETERS; out: kfree(params); } -static struct tee_shm *cmd_alloc_suppl(struct tee_context *ctx, size_t sz) +struct tee_shm *optee_rpc_cmd_alloc_suppl(struct tee_context *ctx, size_t sz) { u32 ret; struct tee_param param; @@ -284,103 +287,7 @@ static struct tee_shm *cmd_alloc_suppl(struct tee_context *ctx, size_t sz) return shm; } -static void handle_rpc_func_cmd_shm_alloc(struct tee_context *ctx, - struct optee_msg_arg *arg, - struct optee_call_ctx *call_ctx) -{ - phys_addr_t pa; - struct tee_shm *shm; - size_t sz; - size_t n; - - arg->ret_origin = TEEC_ORIGIN_COMMS; - - if (!arg->num_params || - arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_INPUT) { - arg->ret = TEEC_ERROR_BAD_PARAMETERS; - return; - } - - for (n = 1; n < arg->num_params; n++) { - if (arg->params[n].attr != OPTEE_MSG_ATTR_TYPE_NONE) { - arg->ret = TEEC_ERROR_BAD_PARAMETERS; - return; - } - } - - sz = arg->params[0].u.value.b; - switch (arg->params[0].u.value.a) { - case OPTEE_RPC_SHM_TYPE_APPL: - shm = cmd_alloc_suppl(ctx, sz); - break; - case OPTEE_RPC_SHM_TYPE_KERNEL: - shm = tee_shm_alloc(ctx, sz, TEE_SHM_MAPPED | TEE_SHM_PRIV); - break; - default: - arg->ret = TEEC_ERROR_BAD_PARAMETERS; - return; - } - - if (IS_ERR(shm)) { - arg->ret = TEEC_ERROR_OUT_OF_MEMORY; - return; - } - - if (tee_shm_get_pa(shm, 0, &pa)) { - arg->ret = TEEC_ERROR_BAD_PARAMETERS; - goto bad; - } - - sz = tee_shm_get_size(shm); - - if (tee_shm_is_registered(shm)) { - struct page **pages; - u64 *pages_list; - size_t page_num; - - pages = tee_shm_get_pages(shm, &page_num); - if (!pages || !page_num) { - arg->ret = TEEC_ERROR_OUT_OF_MEMORY; - goto bad; - } - - pages_list = optee_allocate_pages_list(page_num); - if (!pages_list) { - arg->ret = TEEC_ERROR_OUT_OF_MEMORY; - goto bad; - } - - call_ctx->pages_list = pages_list; - call_ctx->num_entries = page_num; - - arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | - OPTEE_MSG_ATTR_NONCONTIG; - /* - * In the least bits of u.tmem.buf_ptr we store buffer offset - * from 4k page, as described in OP-TEE ABI. - */ - arg->params[0].u.tmem.buf_ptr = virt_to_phys(pages_list) | - (tee_shm_get_page_offset(shm) & - (OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1)); - arg->params[0].u.tmem.size = tee_shm_get_size(shm); - arg->params[0].u.tmem.shm_ref = (unsigned long)shm; - - optee_fill_pages_list(pages_list, pages, page_num, - tee_shm_get_page_offset(shm)); - } else { - arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT; - arg->params[0].u.tmem.buf_ptr = pa; - arg->params[0].u.tmem.size = sz; - arg->params[0].u.tmem.shm_ref = (unsigned long)shm; - } - - arg->ret = TEEC_SUCCESS; - return; -bad: - tee_shm_free(shm); -} - -static void cmd_free_suppl(struct tee_context *ctx, struct tee_shm *shm) +void optee_rpc_cmd_free_suppl(struct tee_context *ctx, struct tee_shm *shm) { struct tee_param param; @@ -405,60 +312,9 @@ static void cmd_free_suppl(struct tee_context *ctx, struct tee_shm *shm) optee_supp_thrd_req(ctx, OPTEE_RPC_CMD_SHM_FREE, 1, ¶m); } -static void handle_rpc_func_cmd_shm_free(struct tee_context *ctx, - struct optee_msg_arg *arg) -{ - struct tee_shm *shm; - - arg->ret_origin = TEEC_ORIGIN_COMMS; - - if (arg->num_params != 1 || - arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_INPUT) { - arg->ret = TEEC_ERROR_BAD_PARAMETERS; - return; - } - - shm = (struct tee_shm *)(unsigned long)arg->params[0].u.value.b; - switch (arg->params[0].u.value.a) { - case OPTEE_RPC_SHM_TYPE_APPL: - cmd_free_suppl(ctx, shm); - break; - case OPTEE_RPC_SHM_TYPE_KERNEL: - tee_shm_free(shm); - break; - default: - arg->ret = TEEC_ERROR_BAD_PARAMETERS; - } - arg->ret = TEEC_SUCCESS; -} - -static void free_pages_list(struct optee_call_ctx *call_ctx) -{ - if (call_ctx->pages_list) { - optee_free_pages_list(call_ctx->pages_list, - call_ctx->num_entries); - call_ctx->pages_list = NULL; - call_ctx->num_entries = 0; - } -} - -void optee_rpc_finalize_call(struct optee_call_ctx *call_ctx) -{ - free_pages_list(call_ctx); -} - -static void handle_rpc_func_cmd(struct tee_context *ctx, struct optee *optee, - struct tee_shm *shm, - struct optee_call_ctx *call_ctx) +void optee_rpc_cmd(struct tee_context *ctx, struct optee *optee, + struct optee_msg_arg *arg) { - struct optee_msg_arg *arg; - - arg = tee_shm_get_va(shm, 0); - if (IS_ERR(arg)) { - pr_err("%s: tee_shm_get_va %p failed\n", __func__, shm); - return; - } - switch (arg->cmd) { case OPTEE_RPC_CMD_GET_TIME: handle_rpc_func_cmd_get_time(arg); @@ -469,73 +325,12 @@ static void handle_rpc_func_cmd(struct tee_context *ctx, struct optee *optee, case OPTEE_RPC_CMD_SUSPEND: handle_rpc_func_cmd_wait(arg); break; - case OPTEE_RPC_CMD_SHM_ALLOC: - free_pages_list(call_ctx); - handle_rpc_func_cmd_shm_alloc(ctx, arg, call_ctx); - break; - case OPTEE_RPC_CMD_SHM_FREE: - handle_rpc_func_cmd_shm_free(ctx, arg); - break; case OPTEE_RPC_CMD_I2C_TRANSFER: handle_rpc_func_cmd_i2c_transfer(ctx, arg); break; default: - handle_rpc_supp_cmd(ctx, arg); + handle_rpc_supp_cmd(ctx, optee, arg); } } -/** - * optee_handle_rpc() - handle RPC from secure world - * @ctx: context doing the RPC - * @param: value of registers for the RPC - * @call_ctx: call context. Preserved during one OP-TEE invocation - * - * Result of RPC is written back into @param. - */ -void optee_handle_rpc(struct tee_context *ctx, struct optee_rpc_param *param, - struct optee_call_ctx *call_ctx) -{ - struct tee_device *teedev = ctx->teedev; - struct optee *optee = tee_get_drvdata(teedev); - struct tee_shm *shm; - phys_addr_t pa; - - switch (OPTEE_SMC_RETURN_GET_RPC_FUNC(param->a0)) { - case OPTEE_SMC_RPC_FUNC_ALLOC: - shm = tee_shm_alloc(ctx, param->a1, - TEE_SHM_MAPPED | TEE_SHM_PRIV); - if (!IS_ERR(shm) && !tee_shm_get_pa(shm, 0, &pa)) { - reg_pair_from_64(¶m->a1, ¶m->a2, pa); - reg_pair_from_64(¶m->a4, ¶m->a5, - (unsigned long)shm); - } else { - param->a1 = 0; - param->a2 = 0; - param->a4 = 0; - param->a5 = 0; - } - break; - case OPTEE_SMC_RPC_FUNC_FREE: - shm = reg_pair_to_ptr(param->a1, param->a2); - tee_shm_free(shm); - break; - case OPTEE_SMC_RPC_FUNC_FOREIGN_INTR: - /* - * A foreign interrupt was raised while secure world was - * executing, since they are handled in Linux a dummy RPC is - * performed to let Linux take the interrupt through the normal - * vector. - */ - break; - case OPTEE_SMC_RPC_FUNC_CMD: - shm = reg_pair_to_ptr(param->a1, param->a2); - handle_rpc_func_cmd(ctx, optee, shm, call_ctx); - break; - default: - pr_warn("Unknown RPC func 0x%x\n", - (u32)OPTEE_SMC_RETURN_GET_RPC_FUNC(param->a0)); - break; - } - param->a0 = OPTEE_SMC_CALL_RETURN_FROM_RPC; -} diff --git a/drivers/tee/optee/shm_pool.c b/drivers/tee/optee/shm_pool.c deleted file mode 100644 index d167039af519..000000000000 --- a/drivers/tee/optee/shm_pool.c +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2015, Linaro Limited - * Copyright (c) 2017, EPAM Systems - */ -#include <linux/device.h> -#include <linux/dma-buf.h> -#include <linux/genalloc.h> -#include <linux/slab.h> -#include <linux/tee_drv.h> -#include "optee_private.h" -#include "optee_smc.h" -#include "shm_pool.h" - -static int pool_op_alloc(struct tee_shm_pool_mgr *poolm, - struct tee_shm *shm, size_t size) -{ - unsigned int order = get_order(size); - struct page *page; - int rc = 0; - - page = alloc_pages(GFP_KERNEL | __GFP_ZERO, order); - if (!page) - return -ENOMEM; - - shm->kaddr = page_address(page); - shm->paddr = page_to_phys(page); - shm->size = PAGE_SIZE << order; - - /* - * Shared memory private to the OP-TEE driver doesn't need - * to be registered with OP-TEE. - */ - if (!(shm->flags & TEE_SHM_PRIV)) { - unsigned int nr_pages = 1 << order, i; - struct page **pages; - - pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL); - if (!pages) { - rc = -ENOMEM; - goto err; - } - - for (i = 0; i < nr_pages; i++) { - pages[i] = page; - page++; - } - - shm->flags |= TEE_SHM_REGISTER; - rc = optee_shm_register(shm->ctx, shm, pages, nr_pages, - (unsigned long)shm->kaddr); - kfree(pages); - if (rc) - goto err; - } - - return 0; - -err: - __free_pages(page, order); - return rc; -} - -static void pool_op_free(struct tee_shm_pool_mgr *poolm, - struct tee_shm *shm) -{ - if (!(shm->flags & TEE_SHM_PRIV)) - optee_shm_unregister(shm->ctx, shm); - - free_pages((unsigned long)shm->kaddr, get_order(shm->size)); - shm->kaddr = NULL; -} - -static void pool_op_destroy_poolmgr(struct tee_shm_pool_mgr *poolm) -{ - kfree(poolm); -} - -static const struct tee_shm_pool_mgr_ops pool_ops = { - .alloc = pool_op_alloc, - .free = pool_op_free, - .destroy_poolmgr = pool_op_destroy_poolmgr, -}; - -/** - * optee_shm_pool_alloc_pages() - create page-based allocator pool - * - * This pool is used when OP-TEE supports dymanic SHM. In this case - * command buffers and such are allocated from kernel's own memory. - */ -struct tee_shm_pool_mgr *optee_shm_pool_alloc_pages(void) -{ - struct tee_shm_pool_mgr *mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); - - if (!mgr) - return ERR_PTR(-ENOMEM); - - mgr->ops = &pool_ops; - - return mgr; -} diff --git a/drivers/tee/optee/shm_pool.h b/drivers/tee/optee/shm_pool.h deleted file mode 100644 index 28109d991c4b..000000000000 --- a/drivers/tee/optee/shm_pool.h +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (c) 2015, Linaro Limited - * Copyright (c) 2016, EPAM Systems - */ - -#ifndef SHM_POOL_H -#define SHM_POOL_H - -#include <linux/tee_drv.h> - -struct tee_shm_pool_mgr *optee_shm_pool_alloc_pages(void); - -#endif diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c new file mode 100644 index 000000000000..6196d7c3888f --- /dev/null +++ b/drivers/tee/optee/smc_abi.c @@ -0,0 +1,1362 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2021, Linaro Limited + * Copyright (c) 2016, EPAM Systems + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/arm-smccc.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/tee_drv.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include "optee_private.h" +#include "optee_smc.h" +#include "optee_rpc_cmd.h" +#define CREATE_TRACE_POINTS +#include "optee_trace.h" + +/* + * This file implement the SMC ABI used when communicating with secure world + * OP-TEE OS via raw SMCs. + * This file is divided into the following sections: + * 1. Convert between struct tee_param and struct optee_msg_param + * 2. Low level support functions to register shared memory in secure world + * 3. Dynamic shared memory pool based on alloc_pages() + * 4. Do a normal scheduled call into secure world + * 5. Driver initialization. + */ + +#define OPTEE_SHM_NUM_PRIV_PAGES CONFIG_OPTEE_SHM_NUM_PRIV_PAGES + +/* + * 1. Convert between struct tee_param and struct optee_msg_param + * + * optee_from_msg_param() and optee_to_msg_param() are the main + * functions. + */ + +static int from_msg_param_tmp_mem(struct tee_param *p, u32 attr, + const struct optee_msg_param *mp) +{ + struct tee_shm *shm; + phys_addr_t pa; + int rc; + + p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT + + attr - OPTEE_MSG_ATTR_TYPE_TMEM_INPUT; + p->u.memref.size = mp->u.tmem.size; + shm = (struct tee_shm *)(unsigned long)mp->u.tmem.shm_ref; + if (!shm) { + p->u.memref.shm_offs = 0; + p->u.memref.shm = NULL; + return 0; + } + + rc = tee_shm_get_pa(shm, 0, &pa); + if (rc) + return rc; + + p->u.memref.shm_offs = mp->u.tmem.buf_ptr - pa; + p->u.memref.shm = shm; + + /* Check that the memref is covered by the shm object */ + if (p->u.memref.size) { + size_t o = p->u.memref.shm_offs + + p->u.memref.size - 1; + + rc = tee_shm_get_pa(shm, o, NULL); + if (rc) + return rc; + } + + return 0; +} + +static void from_msg_param_reg_mem(struct tee_param *p, u32 attr, + const struct optee_msg_param *mp) +{ + struct tee_shm *shm; + + p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT + + attr - OPTEE_MSG_ATTR_TYPE_RMEM_INPUT; + p->u.memref.size = mp->u.rmem.size; + shm = (struct tee_shm *)(unsigned long)mp->u.rmem.shm_ref; + + if (shm) { + p->u.memref.shm_offs = mp->u.rmem.offs; + p->u.memref.shm = shm; + } else { + p->u.memref.shm_offs = 0; + p->u.memref.shm = NULL; + } +} + +/** + * optee_from_msg_param() - convert from OPTEE_MSG parameters to + * struct tee_param + * @optee: main service struct + * @params: subsystem internal parameter representation + * @num_params: number of elements in the parameter arrays + * @msg_params: OPTEE_MSG parameters + * Returns 0 on success or <0 on failure + */ +static int optee_from_msg_param(struct optee *optee, struct tee_param *params, + size_t num_params, + const struct optee_msg_param *msg_params) +{ + int rc; + size_t n; + + for (n = 0; n < num_params; n++) { + struct tee_param *p = params + n; + const struct optee_msg_param *mp = msg_params + n; + u32 attr = mp->attr & OPTEE_MSG_ATTR_TYPE_MASK; + + switch (attr) { + case OPTEE_MSG_ATTR_TYPE_NONE: + p->attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + memset(&p->u, 0, sizeof(p->u)); + break; + case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT: + optee_from_msg_param_value(p, attr, mp); + break; + case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT: + rc = from_msg_param_tmp_mem(p, attr, mp); + if (rc) + return rc; + break; + case OPTEE_MSG_ATTR_TYPE_RMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT: + from_msg_param_reg_mem(p, attr, mp); + break; + + default: + return -EINVAL; + } + } + return 0; +} + +static int to_msg_param_tmp_mem(struct optee_msg_param *mp, + const struct tee_param *p) +{ + int rc; + phys_addr_t pa; + + mp->attr = OPTEE_MSG_ATTR_TYPE_TMEM_INPUT + p->attr - + TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + + mp->u.tmem.shm_ref = (unsigned long)p->u.memref.shm; + mp->u.tmem.size = p->u.memref.size; + + if (!p->u.memref.shm) { + mp->u.tmem.buf_ptr = 0; + return 0; + } + + rc = tee_shm_get_pa(p->u.memref.shm, p->u.memref.shm_offs, &pa); + if (rc) + return rc; + + mp->u.tmem.buf_ptr = pa; + mp->attr |= OPTEE_MSG_ATTR_CACHE_PREDEFINED << + OPTEE_MSG_ATTR_CACHE_SHIFT; + + return 0; +} + +static int to_msg_param_reg_mem(struct optee_msg_param *mp, + const struct tee_param *p) +{ + mp->attr = OPTEE_MSG_ATTR_TYPE_RMEM_INPUT + p->attr - + TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + + mp->u.rmem.shm_ref = (unsigned long)p->u.memref.shm; + mp->u.rmem.size = p->u.memref.size; + mp->u.rmem.offs = p->u.memref.shm_offs; + return 0; +} + +/** + * optee_to_msg_param() - convert from struct tee_params to OPTEE_MSG parameters + * @optee: main service struct + * @msg_params: OPTEE_MSG parameters + * @num_params: number of elements in the parameter arrays + * @params: subsystem itnernal parameter representation + * Returns 0 on success or <0 on failure + */ +static int optee_to_msg_param(struct optee *optee, + struct optee_msg_param *msg_params, + size_t num_params, const struct tee_param *params) +{ + int rc; + size_t n; + + for (n = 0; n < num_params; n++) { + const struct tee_param *p = params + n; + struct optee_msg_param *mp = msg_params + n; + + switch (p->attr) { + case TEE_IOCTL_PARAM_ATTR_TYPE_NONE: + mp->attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + memset(&mp->u, 0, sizeof(mp->u)); + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT: + optee_to_msg_param_value(mp, p); + break; + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT: + case TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT: + if (tee_shm_is_registered(p->u.memref.shm)) + rc = to_msg_param_reg_mem(mp, p); + else + rc = to_msg_param_tmp_mem(mp, p); + if (rc) + return rc; + break; + default: + return -EINVAL; + } + } + return 0; +} + +/* + * 2. Low level support functions to register shared memory in secure world + * + * Functions to enable/disable shared memory caching in secure world, that + * is, lazy freeing of previously allocated shared memory. Freeing is + * performed when a request has been compled. + * + * Functions to register and unregister shared memory both for normal + * clients and for tee-supplicant. + */ + +/** + * optee_enable_shm_cache() - Enables caching of some shared memory allocation + * in OP-TEE + * @optee: main service struct + */ +static void optee_enable_shm_cache(struct optee *optee) +{ + struct optee_call_waiter w; + + /* We need to retry until secure world isn't busy. */ + optee_cq_wait_init(&optee->call_queue, &w); + while (true) { + struct arm_smccc_res res; + + optee->smc.invoke_fn(OPTEE_SMC_ENABLE_SHM_CACHE, + 0, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 == OPTEE_SMC_RETURN_OK) + break; + optee_cq_wait_for_completion(&optee->call_queue, &w); + } + optee_cq_wait_final(&optee->call_queue, &w); +} + +/** + * __optee_disable_shm_cache() - Disables caching of some shared memory + * allocation in OP-TEE + * @optee: main service struct + * @is_mapped: true if the cached shared memory addresses were mapped by this + * kernel, are safe to dereference, and should be freed + */ +static void __optee_disable_shm_cache(struct optee *optee, bool is_mapped) +{ + struct optee_call_waiter w; + + /* We need to retry until secure world isn't busy. */ + optee_cq_wait_init(&optee->call_queue, &w); + while (true) { + union { + struct arm_smccc_res smccc; + struct optee_smc_disable_shm_cache_result result; + } res; + + optee->smc.invoke_fn(OPTEE_SMC_DISABLE_SHM_CACHE, + 0, 0, 0, 0, 0, 0, 0, &res.smccc); + if (res.result.status == OPTEE_SMC_RETURN_ENOTAVAIL) + break; /* All shm's freed */ + if (res.result.status == OPTEE_SMC_RETURN_OK) { + struct tee_shm *shm; + + /* + * Shared memory references that were not mapped by + * this kernel must be ignored to prevent a crash. + */ + if (!is_mapped) + continue; + + shm = reg_pair_to_ptr(res.result.shm_upper32, + res.result.shm_lower32); + tee_shm_free(shm); + } else { + optee_cq_wait_for_completion(&optee->call_queue, &w); + } + } + optee_cq_wait_final(&optee->call_queue, &w); +} + +/** + * optee_disable_shm_cache() - Disables caching of mapped shared memory + * allocations in OP-TEE + * @optee: main service struct + */ +static void optee_disable_shm_cache(struct optee *optee) +{ + return __optee_disable_shm_cache(optee, true); +} + +/** + * optee_disable_unmapped_shm_cache() - Disables caching of shared memory + * allocations in OP-TEE which are not + * currently mapped + * @optee: main service struct + */ +static void optee_disable_unmapped_shm_cache(struct optee *optee) +{ + return __optee_disable_shm_cache(optee, false); +} + +#define PAGELIST_ENTRIES_PER_PAGE \ + ((OPTEE_MSG_NONCONTIG_PAGE_SIZE / sizeof(u64)) - 1) + +/* + * The final entry in each pagelist page is a pointer to the next + * pagelist page. + */ +static size_t get_pages_list_size(size_t num_entries) +{ + int pages = DIV_ROUND_UP(num_entries, PAGELIST_ENTRIES_PER_PAGE); + + return pages * OPTEE_MSG_NONCONTIG_PAGE_SIZE; +} + +static u64 *optee_allocate_pages_list(size_t num_entries) +{ + return alloc_pages_exact(get_pages_list_size(num_entries), GFP_KERNEL); +} + +static void optee_free_pages_list(void *list, size_t num_entries) +{ + free_pages_exact(list, get_pages_list_size(num_entries)); +} + +/** + * optee_fill_pages_list() - write list of user pages to given shared + * buffer. + * + * @dst: page-aligned buffer where list of pages will be stored + * @pages: array of pages that represents shared buffer + * @num_pages: number of entries in @pages + * @page_offset: offset of user buffer from page start + * + * @dst should be big enough to hold list of user page addresses and + * links to the next pages of buffer + */ +static void optee_fill_pages_list(u64 *dst, struct page **pages, int num_pages, + size_t page_offset) +{ + int n = 0; + phys_addr_t optee_page; + /* + * Refer to OPTEE_MSG_ATTR_NONCONTIG description in optee_msg.h + * for details. + */ + struct { + u64 pages_list[PAGELIST_ENTRIES_PER_PAGE]; + u64 next_page_data; + } *pages_data; + + /* + * Currently OP-TEE uses 4k page size and it does not looks + * like this will change in the future. On other hand, there are + * no know ARM architectures with page size < 4k. + * Thus the next built assert looks redundant. But the following + * code heavily relies on this assumption, so it is better be + * safe than sorry. + */ + BUILD_BUG_ON(PAGE_SIZE < OPTEE_MSG_NONCONTIG_PAGE_SIZE); + + pages_data = (void *)dst; + /* + * If linux page is bigger than 4k, and user buffer offset is + * larger than 4k/8k/12k/etc this will skip first 4k pages, + * because they bear no value data for OP-TEE. + */ + optee_page = page_to_phys(*pages) + + round_down(page_offset, OPTEE_MSG_NONCONTIG_PAGE_SIZE); + + while (true) { + pages_data->pages_list[n++] = optee_page; + + if (n == PAGELIST_ENTRIES_PER_PAGE) { + pages_data->next_page_data = + virt_to_phys(pages_data + 1); + pages_data++; + n = 0; + } + + optee_page += OPTEE_MSG_NONCONTIG_PAGE_SIZE; + if (!(optee_page & ~PAGE_MASK)) { + if (!--num_pages) + break; + pages++; + optee_page = page_to_phys(*pages); + } + } +} + +static int optee_shm_register(struct tee_context *ctx, struct tee_shm *shm, + struct page **pages, size_t num_pages, + unsigned long start) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + struct optee_msg_arg *msg_arg; + struct tee_shm *shm_arg; + u64 *pages_list; + int rc; + + if (!num_pages) + return -EINVAL; + + rc = optee_check_mem_type(start, num_pages); + if (rc) + return rc; + + pages_list = optee_allocate_pages_list(num_pages); + if (!pages_list) + return -ENOMEM; + + shm_arg = optee_get_msg_arg(ctx, 1, &msg_arg); + if (IS_ERR(shm_arg)) { + rc = PTR_ERR(shm_arg); + goto out; + } + + optee_fill_pages_list(pages_list, pages, num_pages, + tee_shm_get_page_offset(shm)); + + msg_arg->cmd = OPTEE_MSG_CMD_REGISTER_SHM; + msg_arg->params->attr = OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | + OPTEE_MSG_ATTR_NONCONTIG; + msg_arg->params->u.tmem.shm_ref = (unsigned long)shm; + msg_arg->params->u.tmem.size = tee_shm_get_size(shm); + /* + * In the least bits of msg_arg->params->u.tmem.buf_ptr we + * store buffer offset from 4k page, as described in OP-TEE ABI. + */ + msg_arg->params->u.tmem.buf_ptr = virt_to_phys(pages_list) | + (tee_shm_get_page_offset(shm) & (OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1)); + + if (optee->ops->do_call_with_arg(ctx, shm_arg) || + msg_arg->ret != TEEC_SUCCESS) + rc = -EINVAL; + + tee_shm_free(shm_arg); +out: + optee_free_pages_list(pages_list, num_pages); + return rc; +} + +static int optee_shm_unregister(struct tee_context *ctx, struct tee_shm *shm) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + struct optee_msg_arg *msg_arg; + struct tee_shm *shm_arg; + int rc = 0; + + shm_arg = optee_get_msg_arg(ctx, 1, &msg_arg); + if (IS_ERR(shm_arg)) + return PTR_ERR(shm_arg); + + msg_arg->cmd = OPTEE_MSG_CMD_UNREGISTER_SHM; + + msg_arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_RMEM_INPUT; + msg_arg->params[0].u.rmem.shm_ref = (unsigned long)shm; + + if (optee->ops->do_call_with_arg(ctx, shm_arg) || + msg_arg->ret != TEEC_SUCCESS) + rc = -EINVAL; + tee_shm_free(shm_arg); + return rc; +} + +static int optee_shm_register_supp(struct tee_context *ctx, struct tee_shm *shm, + struct page **pages, size_t num_pages, + unsigned long start) +{ + /* + * We don't want to register supplicant memory in OP-TEE. + * Instead information about it will be passed in RPC code. + */ + return optee_check_mem_type(start, num_pages); +} + +static int optee_shm_unregister_supp(struct tee_context *ctx, + struct tee_shm *shm) +{ + return 0; +} + +/* + * 3. Dynamic shared memory pool based on alloc_pages() + * + * Implements an OP-TEE specific shared memory pool which is used + * when dynamic shared memory is supported by secure world. + * + * The main function is optee_shm_pool_alloc_pages(). + */ + +static int pool_op_alloc(struct tee_shm_pool_mgr *poolm, + struct tee_shm *shm, size_t size) +{ + /* + * Shared memory private to the OP-TEE driver doesn't need + * to be registered with OP-TEE. + */ + if (shm->flags & TEE_SHM_PRIV) + return optee_pool_op_alloc_helper(poolm, shm, size, NULL); + + return optee_pool_op_alloc_helper(poolm, shm, size, optee_shm_register); +} + +static void pool_op_free(struct tee_shm_pool_mgr *poolm, + struct tee_shm *shm) +{ + if (!(shm->flags & TEE_SHM_PRIV)) + optee_shm_unregister(shm->ctx, shm); + + free_pages((unsigned long)shm->kaddr, get_order(shm->size)); + shm->kaddr = NULL; +} + +static void pool_op_destroy_poolmgr(struct tee_shm_pool_mgr *poolm) +{ + kfree(poolm); +} + +static const struct tee_shm_pool_mgr_ops pool_ops = { + .alloc = pool_op_alloc, + .free = pool_op_free, + .destroy_poolmgr = pool_op_destroy_poolmgr, +}; + +/** + * optee_shm_pool_alloc_pages() - create page-based allocator pool + * + * This pool is used when OP-TEE supports dymanic SHM. In this case + * command buffers and such are allocated from kernel's own memory. + */ +static struct tee_shm_pool_mgr *optee_shm_pool_alloc_pages(void) +{ + struct tee_shm_pool_mgr *mgr = kzalloc(sizeof(*mgr), GFP_KERNEL); + + if (!mgr) + return ERR_PTR(-ENOMEM); + + mgr->ops = &pool_ops; + + return mgr; +} + +/* + * 4. Do a normal scheduled call into secure world + * + * The function optee_smc_do_call_with_arg() performs a normal scheduled + * call into secure world. During this call may normal world request help + * from normal world using RPCs, Remote Procedure Calls. This includes + * delivery of non-secure interrupts to for instance allow rescheduling of + * the current task. + */ + +static void handle_rpc_func_cmd_shm_free(struct tee_context *ctx, + struct optee_msg_arg *arg) +{ + struct tee_shm *shm; + + arg->ret_origin = TEEC_ORIGIN_COMMS; + + if (arg->num_params != 1 || + arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_INPUT) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + shm = (struct tee_shm *)(unsigned long)arg->params[0].u.value.b; + switch (arg->params[0].u.value.a) { + case OPTEE_RPC_SHM_TYPE_APPL: + optee_rpc_cmd_free_suppl(ctx, shm); + break; + case OPTEE_RPC_SHM_TYPE_KERNEL: + tee_shm_free(shm); + break; + default: + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + } + arg->ret = TEEC_SUCCESS; +} + +static void handle_rpc_func_cmd_shm_alloc(struct tee_context *ctx, + struct optee_msg_arg *arg, + struct optee_call_ctx *call_ctx) +{ + phys_addr_t pa; + struct tee_shm *shm; + size_t sz; + size_t n; + + arg->ret_origin = TEEC_ORIGIN_COMMS; + + if (!arg->num_params || + arg->params[0].attr != OPTEE_MSG_ATTR_TYPE_VALUE_INPUT) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + for (n = 1; n < arg->num_params; n++) { + if (arg->params[n].attr != OPTEE_MSG_ATTR_TYPE_NONE) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + } + + sz = arg->params[0].u.value.b; + switch (arg->params[0].u.value.a) { + case OPTEE_RPC_SHM_TYPE_APPL: + shm = optee_rpc_cmd_alloc_suppl(ctx, sz); + break; + case OPTEE_RPC_SHM_TYPE_KERNEL: + shm = tee_shm_alloc(ctx, sz, TEE_SHM_MAPPED | TEE_SHM_PRIV); + break; + default: + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + if (IS_ERR(shm)) { + arg->ret = TEEC_ERROR_OUT_OF_MEMORY; + return; + } + + if (tee_shm_get_pa(shm, 0, &pa)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + goto bad; + } + + sz = tee_shm_get_size(shm); + + if (tee_shm_is_registered(shm)) { + struct page **pages; + u64 *pages_list; + size_t page_num; + + pages = tee_shm_get_pages(shm, &page_num); + if (!pages || !page_num) { + arg->ret = TEEC_ERROR_OUT_OF_MEMORY; + goto bad; + } + + pages_list = optee_allocate_pages_list(page_num); + if (!pages_list) { + arg->ret = TEEC_ERROR_OUT_OF_MEMORY; + goto bad; + } + + call_ctx->pages_list = pages_list; + call_ctx->num_entries = page_num; + + arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | + OPTEE_MSG_ATTR_NONCONTIG; + /* + * In the least bits of u.tmem.buf_ptr we store buffer offset + * from 4k page, as described in OP-TEE ABI. + */ + arg->params[0].u.tmem.buf_ptr = virt_to_phys(pages_list) | + (tee_shm_get_page_offset(shm) & + (OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1)); + arg->params[0].u.tmem.size = tee_shm_get_size(shm); + arg->params[0].u.tmem.shm_ref = (unsigned long)shm; + + optee_fill_pages_list(pages_list, pages, page_num, + tee_shm_get_page_offset(shm)); + } else { + arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT; + arg->params[0].u.tmem.buf_ptr = pa; + arg->params[0].u.tmem.size = sz; + arg->params[0].u.tmem.shm_ref = (unsigned long)shm; + } + + arg->ret = TEEC_SUCCESS; + return; +bad: + tee_shm_free(shm); +} + +static void free_pages_list(struct optee_call_ctx *call_ctx) +{ + if (call_ctx->pages_list) { + optee_free_pages_list(call_ctx->pages_list, + call_ctx->num_entries); + call_ctx->pages_list = NULL; + call_ctx->num_entries = 0; + } +} + +static void optee_rpc_finalize_call(struct optee_call_ctx *call_ctx) +{ + free_pages_list(call_ctx); +} + +static void handle_rpc_func_cmd(struct tee_context *ctx, struct optee *optee, + struct tee_shm *shm, + struct optee_call_ctx *call_ctx) +{ + struct optee_msg_arg *arg; + + arg = tee_shm_get_va(shm, 0); + if (IS_ERR(arg)) { + pr_err("%s: tee_shm_get_va %p failed\n", __func__, shm); + return; + } + + switch (arg->cmd) { + case OPTEE_RPC_CMD_SHM_ALLOC: + free_pages_list(call_ctx); + handle_rpc_func_cmd_shm_alloc(ctx, arg, call_ctx); + break; + case OPTEE_RPC_CMD_SHM_FREE: + handle_rpc_func_cmd_shm_free(ctx, arg); + break; + default: + optee_rpc_cmd(ctx, optee, arg); + } +} + +/** + * optee_handle_rpc() - handle RPC from secure world + * @ctx: context doing the RPC + * @param: value of registers for the RPC + * @call_ctx: call context. Preserved during one OP-TEE invocation + * + * Result of RPC is written back into @param. + */ +static void optee_handle_rpc(struct tee_context *ctx, + struct optee_rpc_param *param, + struct optee_call_ctx *call_ctx) +{ + struct tee_device *teedev = ctx->teedev; + struct optee *optee = tee_get_drvdata(teedev); + struct tee_shm *shm; + phys_addr_t pa; + + switch (OPTEE_SMC_RETURN_GET_RPC_FUNC(param->a0)) { + case OPTEE_SMC_RPC_FUNC_ALLOC: + shm = tee_shm_alloc(ctx, param->a1, + TEE_SHM_MAPPED | TEE_SHM_PRIV); + if (!IS_ERR(shm) && !tee_shm_get_pa(shm, 0, &pa)) { + reg_pair_from_64(¶m->a1, ¶m->a2, pa); + reg_pair_from_64(¶m->a4, ¶m->a5, + (unsigned long)shm); + } else { + param->a1 = 0; + param->a2 = 0; + param->a4 = 0; + param->a5 = 0; + } + break; + case OPTEE_SMC_RPC_FUNC_FREE: + shm = reg_pair_to_ptr(param->a1, param->a2); + tee_shm_free(shm); + break; + case OPTEE_SMC_RPC_FUNC_FOREIGN_INTR: + /* + * A foreign interrupt was raised while secure world was + * executing, since they are handled in Linux a dummy RPC is + * performed to let Linux take the interrupt through the normal + * vector. + */ + break; + case OPTEE_SMC_RPC_FUNC_CMD: + shm = reg_pair_to_ptr(param->a1, param->a2); + handle_rpc_func_cmd(ctx, optee, shm, call_ctx); + break; + default: + pr_warn("Unknown RPC func 0x%x\n", + (u32)OPTEE_SMC_RETURN_GET_RPC_FUNC(param->a0)); + break; + } + + param->a0 = OPTEE_SMC_CALL_RETURN_FROM_RPC; +} + +/** + * optee_smc_do_call_with_arg() - Do an SMC to OP-TEE in secure world + * @ctx: calling context + * @arg: shared memory holding the message to pass to secure world + * + * Does and SMC to OP-TEE in secure world and handles eventual resulting + * Remote Procedure Calls (RPC) from OP-TEE. + * + * Returns return code from secure world, 0 is OK + */ +static int optee_smc_do_call_with_arg(struct tee_context *ctx, + struct tee_shm *arg) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + struct optee_call_waiter w; + struct optee_rpc_param param = { }; + struct optee_call_ctx call_ctx = { }; + phys_addr_t parg; + int rc; + + rc = tee_shm_get_pa(arg, 0, &parg); + if (rc) + return rc; + + param.a0 = OPTEE_SMC_CALL_WITH_ARG; + reg_pair_from_64(¶m.a1, ¶m.a2, parg); + /* Initialize waiter */ + optee_cq_wait_init(&optee->call_queue, &w); + while (true) { + struct arm_smccc_res res; + + trace_optee_invoke_fn_begin(¶m); + optee->smc.invoke_fn(param.a0, param.a1, param.a2, param.a3, + param.a4, param.a5, param.a6, param.a7, + &res); + trace_optee_invoke_fn_end(¶m, &res); + + if (res.a0 == OPTEE_SMC_RETURN_ETHREAD_LIMIT) { + /* + * Out of threads in secure world, wait for a thread + * become available. + */ + optee_cq_wait_for_completion(&optee->call_queue, &w); + } else if (OPTEE_SMC_RETURN_IS_RPC(res.a0)) { + cond_resched(); + param.a0 = res.a0; + param.a1 = res.a1; + param.a2 = res.a2; + param.a3 = res.a3; + optee_handle_rpc(ctx, ¶m, &call_ctx); + } else { + rc = res.a0; + break; + } + } + + optee_rpc_finalize_call(&call_ctx); + /* + * We're done with our thread in secure world, if there's any + * thread waiters wake up one. + */ + optee_cq_wait_final(&optee->call_queue, &w); + + return rc; +} + +/* + * 5. Driver initialization + * + * During driver inititialization is secure world probed to find out which + * features it supports so the driver can be initialized with a matching + * configuration. This involves for instance support for dynamic shared + * memory instead of a static memory carvout. + */ + +static void optee_get_version(struct tee_device *teedev, + struct tee_ioctl_version_data *vers) +{ + struct tee_ioctl_version_data v = { + .impl_id = TEE_IMPL_ID_OPTEE, + .impl_caps = TEE_OPTEE_CAP_TZ, + .gen_caps = TEE_GEN_CAP_GP, + }; + struct optee *optee = tee_get_drvdata(teedev); + + if (optee->smc.sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) + v.gen_caps |= TEE_GEN_CAP_REG_MEM; + if (optee->smc.sec_caps & OPTEE_SMC_SEC_CAP_MEMREF_NULL) + v.gen_caps |= TEE_GEN_CAP_MEMREF_NULL; + *vers = v; +} + +static int optee_smc_open(struct tee_context *ctx) +{ + struct optee *optee = tee_get_drvdata(ctx->teedev); + u32 sec_caps = optee->smc.sec_caps; + + return optee_open(ctx, sec_caps & OPTEE_SMC_SEC_CAP_MEMREF_NULL); +} + +static const struct tee_driver_ops optee_clnt_ops = { + .get_version = optee_get_version, + .open = optee_smc_open, + .release = optee_release, + .open_session = optee_open_session, + .close_session = optee_close_session, + .invoke_func = optee_invoke_func, + .cancel_req = optee_cancel_req, + .shm_register = optee_shm_register, + .shm_unregister = optee_shm_unregister, +}; + +static const struct tee_desc optee_clnt_desc = { + .name = DRIVER_NAME "-clnt", + .ops = &optee_clnt_ops, + .owner = THIS_MODULE, +}; + +static const struct tee_driver_ops optee_supp_ops = { + .get_version = optee_get_version, + .open = optee_smc_open, + .release = optee_release_supp, + .supp_recv = optee_supp_recv, + .supp_send = optee_supp_send, + .shm_register = optee_shm_register_supp, + .shm_unregister = optee_shm_unregister_supp, +}; + +static const struct tee_desc optee_supp_desc = { + .name = DRIVER_NAME "-supp", + .ops = &optee_supp_ops, + .owner = THIS_MODULE, + .flags = TEE_DESC_PRIVILEGED, +}; + +static const struct optee_ops optee_ops = { + .do_call_with_arg = optee_smc_do_call_with_arg, + .to_msg_param = optee_to_msg_param, + .from_msg_param = optee_from_msg_param, +}; + +static bool optee_msg_api_uid_is_optee_api(optee_invoke_fn *invoke_fn) +{ + struct arm_smccc_res res; + + invoke_fn(OPTEE_SMC_CALLS_UID, 0, 0, 0, 0, 0, 0, 0, &res); + + if (res.a0 == OPTEE_MSG_UID_0 && res.a1 == OPTEE_MSG_UID_1 && + res.a2 == OPTEE_MSG_UID_2 && res.a3 == OPTEE_MSG_UID_3) + return true; + return false; +} + +static void optee_msg_get_os_revision(optee_invoke_fn *invoke_fn) +{ + union { + struct arm_smccc_res smccc; + struct optee_smc_call_get_os_revision_result result; + } res = { + .result = { + .build_id = 0 + } + }; + + invoke_fn(OPTEE_SMC_CALL_GET_OS_REVISION, 0, 0, 0, 0, 0, 0, 0, + &res.smccc); + + if (res.result.build_id) + pr_info("revision %lu.%lu (%08lx)", res.result.major, + res.result.minor, res.result.build_id); + else + pr_info("revision %lu.%lu", res.result.major, res.result.minor); +} + +static bool optee_msg_api_revision_is_compatible(optee_invoke_fn *invoke_fn) +{ + union { + struct arm_smccc_res smccc; + struct optee_smc_calls_revision_result result; + } res; + + invoke_fn(OPTEE_SMC_CALLS_REVISION, 0, 0, 0, 0, 0, 0, 0, &res.smccc); + + if (res.result.major == OPTEE_MSG_REVISION_MAJOR && + (int)res.result.minor >= OPTEE_MSG_REVISION_MINOR) + return true; + return false; +} + +static bool optee_msg_exchange_capabilities(optee_invoke_fn *invoke_fn, + u32 *sec_caps) +{ + union { + struct arm_smccc_res smccc; + struct optee_smc_exchange_capabilities_result result; + } res; + u32 a1 = 0; + + /* + * TODO This isn't enough to tell if it's UP system (from kernel + * point of view) or not, is_smp() returns the information + * needed, but can't be called directly from here. + */ + if (!IS_ENABLED(CONFIG_SMP) || nr_cpu_ids == 1) + a1 |= OPTEE_SMC_NSEC_CAP_UNIPROCESSOR; + + invoke_fn(OPTEE_SMC_EXCHANGE_CAPABILITIES, a1, 0, 0, 0, 0, 0, 0, + &res.smccc); + + if (res.result.status != OPTEE_SMC_RETURN_OK) + return false; + + *sec_caps = res.result.capabilities; + return true; +} + +static struct tee_shm_pool *optee_config_dyn_shm(void) +{ + struct tee_shm_pool_mgr *priv_mgr; + struct tee_shm_pool_mgr *dmabuf_mgr; + void *rc; + + rc = optee_shm_pool_alloc_pages(); + if (IS_ERR(rc)) + return rc; + priv_mgr = rc; + + rc = optee_shm_pool_alloc_pages(); + if (IS_ERR(rc)) { + tee_shm_pool_mgr_destroy(priv_mgr); + return rc; + } + dmabuf_mgr = rc; + + rc = tee_shm_pool_alloc(priv_mgr, dmabuf_mgr); + if (IS_ERR(rc)) { + tee_shm_pool_mgr_destroy(priv_mgr); + tee_shm_pool_mgr_destroy(dmabuf_mgr); + } + + return rc; +} + +static struct tee_shm_pool * +optee_config_shm_memremap(optee_invoke_fn *invoke_fn, void **memremaped_shm) +{ + union { + struct arm_smccc_res smccc; + struct optee_smc_get_shm_config_result result; + } res; + unsigned long vaddr; + phys_addr_t paddr; + size_t size; + phys_addr_t begin; + phys_addr_t end; + void *va; + struct tee_shm_pool_mgr *priv_mgr; + struct tee_shm_pool_mgr *dmabuf_mgr; + void *rc; + const int sz = OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE; + + invoke_fn(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc); + if (res.result.status != OPTEE_SMC_RETURN_OK) { + pr_err("static shm service not available\n"); + return ERR_PTR(-ENOENT); + } + + if (res.result.settings != OPTEE_SMC_SHM_CACHED) { + pr_err("only normal cached shared memory supported\n"); + return ERR_PTR(-EINVAL); + } + + begin = roundup(res.result.start, PAGE_SIZE); + end = rounddown(res.result.start + res.result.size, PAGE_SIZE); + paddr = begin; + size = end - begin; + + if (size < 2 * OPTEE_SHM_NUM_PRIV_PAGES * PAGE_SIZE) { + pr_err("too small shared memory area\n"); + return ERR_PTR(-EINVAL); + } + + va = memremap(paddr, size, MEMREMAP_WB); + if (!va) { + pr_err("shared memory ioremap failed\n"); + return ERR_PTR(-EINVAL); + } + vaddr = (unsigned long)va; + + rc = tee_shm_pool_mgr_alloc_res_mem(vaddr, paddr, sz, + 3 /* 8 bytes aligned */); + if (IS_ERR(rc)) + goto err_memunmap; + priv_mgr = rc; + + vaddr += sz; + paddr += sz; + size -= sz; + + rc = tee_shm_pool_mgr_alloc_res_mem(vaddr, paddr, size, PAGE_SHIFT); + if (IS_ERR(rc)) + goto err_free_priv_mgr; + dmabuf_mgr = rc; + + rc = tee_shm_pool_alloc(priv_mgr, dmabuf_mgr); + if (IS_ERR(rc)) + goto err_free_dmabuf_mgr; + + *memremaped_shm = va; + + return rc; + +err_free_dmabuf_mgr: + tee_shm_pool_mgr_destroy(dmabuf_mgr); +err_free_priv_mgr: + tee_shm_pool_mgr_destroy(priv_mgr); +err_memunmap: + memunmap(va); + return rc; +} + +/* Simple wrapper functions to be able to use a function pointer */ +static void optee_smccc_smc(unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, + unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res); +} + +static void optee_smccc_hvc(unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, + unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); +} + +static optee_invoke_fn *get_invoke_func(struct device *dev) +{ + const char *method; + + pr_info("probing for conduit method.\n"); + + if (device_property_read_string(dev, "method", &method)) { + pr_warn("missing \"method\" property\n"); + return ERR_PTR(-ENXIO); + } + + if (!strcmp("hvc", method)) + return optee_smccc_hvc; + else if (!strcmp("smc", method)) + return optee_smccc_smc; + + pr_warn("invalid \"method\" property: %s\n", method); + return ERR_PTR(-EINVAL); +} + +/* optee_remove - Device Removal Routine + * @pdev: platform device information struct + * + * optee_remove is called by platform subsystem to alert the driver + * that it should release the device + */ +static int optee_smc_remove(struct platform_device *pdev) +{ + struct optee *optee = platform_get_drvdata(pdev); + + /* + * Ask OP-TEE to free all cached shared memory objects to decrease + * reference counters and also avoid wild pointers in secure world + * into the old shared memory range. + */ + optee_disable_shm_cache(optee); + + optee_remove_common(optee); + + if (optee->smc.memremaped_shm) + memunmap(optee->smc.memremaped_shm); + + kfree(optee); + + return 0; +} + +/* optee_shutdown - Device Removal Routine + * @pdev: platform device information struct + * + * platform_shutdown is called by the platform subsystem to alert + * the driver that a shutdown, reboot, or kexec is happening and + * device must be disabled. + */ +static void optee_shutdown(struct platform_device *pdev) +{ + optee_disable_shm_cache(platform_get_drvdata(pdev)); +} + +static int optee_probe(struct platform_device *pdev) +{ + optee_invoke_fn *invoke_fn; + struct tee_shm_pool *pool = ERR_PTR(-EINVAL); + struct optee *optee = NULL; + void *memremaped_shm = NULL; + struct tee_device *teedev; + u32 sec_caps; + int rc; + + invoke_fn = get_invoke_func(&pdev->dev); + if (IS_ERR(invoke_fn)) + return PTR_ERR(invoke_fn); + + if (!optee_msg_api_uid_is_optee_api(invoke_fn)) { + pr_warn("api uid mismatch\n"); + return -EINVAL; + } + + optee_msg_get_os_revision(invoke_fn); + + if (!optee_msg_api_revision_is_compatible(invoke_fn)) { + pr_warn("api revision mismatch\n"); + return -EINVAL; + } + + if (!optee_msg_exchange_capabilities(invoke_fn, &sec_caps)) { + pr_warn("capabilities mismatch\n"); + return -EINVAL; + } + + /* + * Try to use dynamic shared memory if possible + */ + if (sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) + pool = optee_config_dyn_shm(); + + /* + * If dynamic shared memory is not available or failed - try static one + */ + if (IS_ERR(pool) && (sec_caps & OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM)) + pool = optee_config_shm_memremap(invoke_fn, &memremaped_shm); + + if (IS_ERR(pool)) + return PTR_ERR(pool); + + optee = kzalloc(sizeof(*optee), GFP_KERNEL); + if (!optee) { + rc = -ENOMEM; + goto err; + } + + optee->ops = &optee_ops; + optee->smc.invoke_fn = invoke_fn; + optee->smc.sec_caps = sec_caps; + + teedev = tee_device_alloc(&optee_clnt_desc, NULL, pool, optee); + if (IS_ERR(teedev)) { + rc = PTR_ERR(teedev); + goto err; + } + optee->teedev = teedev; + + teedev = tee_device_alloc(&optee_supp_desc, NULL, pool, optee); + if (IS_ERR(teedev)) { + rc = PTR_ERR(teedev); + goto err; + } + optee->supp_teedev = teedev; + + rc = tee_device_register(optee->teedev); + if (rc) + goto err; + + rc = tee_device_register(optee->supp_teedev); + if (rc) + goto err; + + mutex_init(&optee->call_queue.mutex); + INIT_LIST_HEAD(&optee->call_queue.waiters); + optee_wait_queue_init(&optee->wait_queue); + optee_supp_init(&optee->supp); + optee->smc.memremaped_shm = memremaped_shm; + optee->pool = pool; + + /* + * Ensure that there are no pre-existing shm objects before enabling + * the shm cache so that there's no chance of receiving an invalid + * address during shutdown. This could occur, for example, if we're + * kexec booting from an older kernel that did not properly cleanup the + * shm cache. + */ + optee_disable_unmapped_shm_cache(optee); + + optee_enable_shm_cache(optee); + + if (optee->smc.sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) + pr_info("dynamic shared memory is enabled\n"); + + platform_set_drvdata(pdev, optee); + + rc = optee_enumerate_devices(PTA_CMD_GET_DEVICES); + if (rc) { + optee_smc_remove(pdev); + return rc; + } + + pr_info("initialized driver\n"); + return 0; +err: + if (optee) { + /* + * tee_device_unregister() is safe to call even if the + * devices hasn't been registered with + * tee_device_register() yet. + */ + tee_device_unregister(optee->supp_teedev); + tee_device_unregister(optee->teedev); + kfree(optee); + } + if (pool) + tee_shm_pool_free(pool); + if (memremaped_shm) + memunmap(memremaped_shm); + return rc; +} + +static const struct of_device_id optee_dt_match[] = { + { .compatible = "linaro,optee-tz" }, + {}, +}; +MODULE_DEVICE_TABLE(of, optee_dt_match); + +static struct platform_driver optee_driver = { + .probe = optee_probe, + .remove = optee_smc_remove, + .shutdown = optee_shutdown, + .driver = { + .name = "optee", + .of_match_table = optee_dt_match, + }, +}; + +int optee_smc_abi_register(void) +{ + return platform_driver_register(&optee_driver); +} + +void optee_smc_abi_unregister(void) +{ + platform_driver_unregister(&optee_driver); +} |