From 0a728e0bda7c14921723362af86b0ba30e5d6c79 Mon Sep 17 00:00:00 2001 From: Nagarjuna Kristam Date: Tue, 3 Sep 2019 16:26:52 +0530 Subject: soc/tegra: fuse: Add FUSE clock check in tegra_fuse_readl() tegra_fuse_readl() can be called from drivers at any time. If this API is called before tegra_fuse_probe(), we end up enabling the clock before it is registered. Add a check for the FUSE clock in tegra_fuse_readl() and propagate any errors. Signed-off-by: Nagarjuna Kristam Acked-by: Thierry Reding Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/fuse-tegra.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c index 3eb44e65b326..58996c6ea767 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra.c +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -186,9 +186,12 @@ u32 __init tegra_fuse_read_early(unsigned int offset) int tegra_fuse_readl(unsigned long offset, u32 *value) { - if (!fuse->read) + if (!fuse->read || !fuse->clk) return -EPROBE_DEFER; + if (IS_ERR(fuse->clk)) + return PTR_ERR(fuse->clk); + *value = fuse->read(fuse, offset); return 0; -- cgit From c9e753767a9c75d2044fb7343950a6a992d34a16 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 1 Oct 2019 13:48:29 +0200 Subject: soc/tegra: pmc: Fix crashes for hierarchical interrupts Interrupts that don't have an associated wake event or GPIO wake events end up with an associate IRQ chip that is NULL and which causes IRQ code to crash. This is because we don't implicitly set the parent IRQ chip by allocating the interrupt at the parent. However, there really isn't a corresponding interrupt at the parent, so we need to work around this by setting the special no_irq_chip as the IRQ chip for these interrupts. Fixes: 19906e6b1667 ("soc/tegra: pmc: Add wake event support") Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 9f9c1c677cf4..0447afa970f5 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -1899,6 +1899,20 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq, event->id, &pmc->irq, pmc); + /* + * GPIOs don't have an equivalent interrupt in the + * parent controller (GIC). However some code, such + * as the one in irq_get_irqchip_state(), require a + * valid IRQ chip to be set. Make sure that's the + * case by passing NULL here, which will install a + * dummy IRQ chip for the interrupt in the parent + * domain. + */ + if (domain->parent) + irq_domain_set_hwirq_and_chip(domain->parent, + virq, 0, NULL, + NULL); + break; } } @@ -1908,10 +1922,22 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq, * dummy hardware IRQ number. This is used in the ->irq_set_type() * and ->irq_set_wake() callbacks to return early for these IRQs. */ - if (i == soc->num_wake_events) + if (i == soc->num_wake_events) { err = irq_domain_set_hwirq_and_chip(domain, virq, ULONG_MAX, &pmc->irq, pmc); + /* + * Interrupts without a wake event don't have a corresponding + * interrupt in the parent controller (GIC). Pass NULL for the + * chip here, which causes a dummy IRQ chip to be installed + * for the interrupt in the parent domain, to make this + * explicit. + */ + if (domain->parent) + irq_domain_set_hwirq_and_chip(domain->parent, virq, 0, + NULL, NULL); + } + return err; } -- cgit From 9f1022b8bd14ce937ca56174a97a1ce475c07693 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 20 Aug 2019 15:53:44 +0200 Subject: soc/tegra: fuse: Restore base on sysfs failure Make sure to also restore the register base address on sysfs registration failure. Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/fuse-tegra.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c index 58996c6ea767..db516a2a3807 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra.c +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -146,20 +146,24 @@ static int tegra_fuse_probe(struct platform_device *pdev) if (fuse->soc->probe) { err = fuse->soc->probe(fuse); - if (err < 0) { - fuse->base = base; - return err; - } + if (err < 0) + goto restore; } if (tegra_fuse_create_sysfs(&pdev->dev, fuse->soc->info->size, - fuse->soc->info)) - return -ENODEV; + fuse->soc->info)) { + err = -ENODEV; + goto restore; + } /* release the early I/O memory mapping */ iounmap(base); return 0; + +restore: + fuse->base = base; + return err; } static struct platform_driver tegra_fuse_driver = { -- cgit From 96ee12b2a203167ffda7ac4a444418ca53df056d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 20 Aug 2019 15:57:16 +0200 Subject: soc/tegra: fuse: Implement nvmem device The nvmem framework provides a generic infrastructure and API to access the type of information stored in fuses such as the Tegra FUSE block. Implement an nvmem device that can be used to access the information in a more generic way to decouple consumers from the custom Tegra API and to add a more formal way of creating the dependency between the FUSE device and the consumers. Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/fuse-tegra.c | 82 ++++++++++++++++--------------------- drivers/soc/tegra/fuse/fuse.h | 3 ++ 2 files changed, 38 insertions(+), 47 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c index db516a2a3807..430a47963a57 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra.c +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -31,50 +33,6 @@ static const char *tegra_revision_name[TEGRA_REVISION_MAX] = { [TEGRA_REVISION_A04] = "A04", }; -static u8 fuse_readb(struct tegra_fuse *fuse, unsigned int offset) -{ - u32 val; - - val = fuse->read(fuse, round_down(offset, 4)); - val >>= (offset % 4) * 8; - val &= 0xff; - - return val; -} - -static ssize_t fuse_read(struct file *fd, struct kobject *kobj, - struct bin_attribute *attr, char *buf, - loff_t pos, size_t size) -{ - struct device *dev = kobj_to_dev(kobj); - struct tegra_fuse *fuse = dev_get_drvdata(dev); - int i; - - if (pos < 0 || pos >= attr->size) - return 0; - - if (size > attr->size - pos) - size = attr->size - pos; - - for (i = 0; i < size; i++) - buf[i] = fuse_readb(fuse, pos + i); - - return i; -} - -static struct bin_attribute fuse_bin_attr = { - .attr = { .name = "fuse", .mode = S_IRUGO, }, - .read = fuse_read, -}; - -static int tegra_fuse_create_sysfs(struct device *dev, unsigned int size, - const struct tegra_fuse_info *info) -{ - fuse_bin_attr.size = size; - - return device_create_bin_file(dev, &fuse_bin_attr); -} - static const struct of_device_id car_match[] __initconst = { { .compatible = "nvidia,tegra20-car", }, { .compatible = "nvidia,tegra30-car", }, @@ -115,9 +73,23 @@ static const struct of_device_id tegra_fuse_match[] = { { /* sentinel */ } }; +static int tegra_fuse_read(void *priv, unsigned int offset, void *value, + size_t bytes) +{ + unsigned int count = bytes / 4, i; + struct tegra_fuse *fuse = priv; + u32 *buffer = value; + + for (i = 0; i < count; i++) + buffer[i] = fuse->read(fuse, offset + i * 4); + + return 0; +} + static int tegra_fuse_probe(struct platform_device *pdev) { void __iomem *base = fuse->base; + struct nvmem_config nvmem; struct resource *res; int err; @@ -150,9 +122,25 @@ static int tegra_fuse_probe(struct platform_device *pdev) goto restore; } - if (tegra_fuse_create_sysfs(&pdev->dev, fuse->soc->info->size, - fuse->soc->info)) { - err = -ENODEV; + memset(&nvmem, 0, sizeof(nvmem)); + nvmem.dev = &pdev->dev; + nvmem.name = "fuse"; + nvmem.id = -1; + nvmem.owner = THIS_MODULE; + nvmem.type = NVMEM_TYPE_OTP; + nvmem.read_only = true; + nvmem.root_only = true; + nvmem.reg_read = tegra_fuse_read; + nvmem.size = fuse->soc->info->size; + nvmem.word_size = 4; + nvmem.stride = 4; + nvmem.priv = fuse; + + fuse->nvmem = devm_nvmem_register(&pdev->dev, &nvmem); + if (IS_ERR(fuse->nvmem)) { + err = PTR_ERR(fuse->nvmem); + dev_err(&pdev->dev, "failed to register NVMEM device: %d\n", + err); goto restore; } diff --git a/drivers/soc/tegra/fuse/fuse.h b/drivers/soc/tegra/fuse/fuse.h index 7230cb330503..32bf6c070ae7 100644 --- a/drivers/soc/tegra/fuse/fuse.h +++ b/drivers/soc/tegra/fuse/fuse.h @@ -13,6 +13,7 @@ #include #include +struct nvmem_device; struct tegra_fuse; struct tegra_fuse_info { @@ -48,6 +49,8 @@ struct tegra_fuse { dma_addr_t phys; u32 *virt; } apbdma; + + struct nvmem_device *nvmem; }; void tegra_init_revision(void); -- cgit From f4619c7f68ba02f8357a9d42340a6dc8a229268d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 20 Aug 2019 15:59:49 +0200 Subject: soc/tegra: fuse: Add cell information Create nvmem cells for all the fuses currently used by consumers. Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/fuse-tegra.c | 90 +++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c index 430a47963a57..cbe3d6f19074 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra.c +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -86,6 +86,94 @@ static int tegra_fuse_read(void *priv, unsigned int offset, void *value, return 0; } +static const struct nvmem_cell_info tegra_fuse_cells[] = { + { + .name = "tsensor-cpu1", + .offset = 0x084, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "tsensor-cpu2", + .offset = 0x088, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "tsensor-cpu0", + .offset = 0x098, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "xusb-pad-calibration", + .offset = 0x0f0, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "tsensor-cpu3", + .offset = 0x12c, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "sata-calibration", + .offset = 0x124, + .bytes = 1, + .bit_offset = 0, + .nbits = 2, + }, { + .name = "tsensor-gpu", + .offset = 0x154, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "tsensor-mem0", + .offset = 0x158, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "tsensor-mem1", + .offset = 0x15c, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "tsensor-pllx", + .offset = 0x160, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "tsensor-common", + .offset = 0x180, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "tsensor-realignment", + .offset = 0x1fc, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "gpu-calibration", + .offset = 0x204, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, { + .name = "xusb-pad-calibration-ext", + .offset = 0x250, + .bytes = 4, + .bit_offset = 0, + .nbits = 32, + }, +}; + static int tegra_fuse_probe(struct platform_device *pdev) { void __iomem *base = fuse->base; @@ -127,6 +215,8 @@ static int tegra_fuse_probe(struct platform_device *pdev) nvmem.name = "fuse"; nvmem.id = -1; nvmem.owner = THIS_MODULE; + nvmem.cells = tegra_fuse_cells; + nvmem.ncells = ARRAY_SIZE(tegra_fuse_cells); nvmem.type = NVMEM_TYPE_OTP; nvmem.read_only = true; nvmem.root_only = true; -- cgit From 9f94fadd75d34acec19c164ffb1b60c66d72f898 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 20 Aug 2019 16:01:04 +0200 Subject: soc/tegra: fuse: Register cell lookups for compatibility Typically nvmem cells would be stored in device tree. However, for compatibility with device trees that don't contain nvmem cell definitions, register lookups for cells currently used by consumers. This allows the consumers to use the same API to query cells from the device tree or using the legacy mechanism. Signed-off-by: Thierry Reding --- drivers/soc/tegra/fuse/fuse-tegra.c | 9 ++ drivers/soc/tegra/fuse/fuse-tegra30.c | 154 ++++++++++++++++++++++++++++++++++ drivers/soc/tegra/fuse/fuse.h | 5 ++ 3 files changed, 168 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c index cbe3d6f19074..4d719d4b8d5a 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra.c +++ b/drivers/soc/tegra/fuse/fuse-tegra.c @@ -423,6 +423,15 @@ static int __init tegra_init_fuse(void) pr_debug("Tegra CPU Speedo ID %d, SoC Speedo ID %d\n", tegra_sku_info.cpu_speedo_id, tegra_sku_info.soc_speedo_id); + if (fuse->soc->lookups) { + size_t size = sizeof(*fuse->lookups) * fuse->soc->num_lookups; + + fuse->lookups = kmemdup(fuse->soc->lookups, size, GFP_KERNEL); + if (!fuse->lookups) + return -ENOMEM; + + nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups); + } return 0; } diff --git a/drivers/soc/tegra/fuse/fuse-tegra30.c b/drivers/soc/tegra/fuse/fuse-tegra30.c index be9424a87173..b8daaf5b7291 100644 --- a/drivers/soc/tegra/fuse/fuse-tegra30.c +++ b/drivers/soc/tegra/fuse/fuse-tegra30.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +128,70 @@ const struct tegra_fuse_soc tegra114_fuse_soc = { #endif #if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC) +static const struct nvmem_cell_lookup tegra124_fuse_lookups[] = { + { + .nvmem_name = "fuse", + .cell_name = "xusb-pad-calibration", + .dev_id = "7009f000.padctl", + .con_id = "calibration", + }, { + .nvmem_name = "fuse", + .cell_name = "sata-calibration", + .dev_id = "70020000.sata", + .con_id = "calibration", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-common", + .dev_id = "700e2000.thermal-sensor", + .con_id = "common", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-realignment", + .dev_id = "700e2000.thermal-sensor", + .con_id = "realignment", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-cpu0", + .dev_id = "700e2000.thermal-sensor", + .con_id = "cpu0", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-cpu1", + .dev_id = "700e2000.thermal-sensor", + .con_id = "cpu1", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-cpu2", + .dev_id = "700e2000.thermal-sensor", + .con_id = "cpu2", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-cpu3", + .dev_id = "700e2000.thermal-sensor", + .con_id = "cpu3", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-mem0", + .dev_id = "700e2000.thermal-sensor", + .con_id = "mem0", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-mem1", + .dev_id = "700e2000.thermal-sensor", + .con_id = "mem1", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-gpu", + .dev_id = "700e2000.thermal-sensor", + .con_id = "gpu", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-pllx", + .dev_id = "700e2000.thermal-sensor", + .con_id = "pllx", + }, +}; + static const struct tegra_fuse_info tegra124_fuse_info = { .read = tegra30_fuse_read, .size = 0x300, @@ -137,10 +202,81 @@ const struct tegra_fuse_soc tegra124_fuse_soc = { .init = tegra30_fuse_init, .speedo_init = tegra124_init_speedo_data, .info = &tegra124_fuse_info, + .lookups = tegra124_fuse_lookups, + .num_lookups = ARRAY_SIZE(tegra124_fuse_lookups), }; #endif #if defined(CONFIG_ARCH_TEGRA_210_SOC) +static const struct nvmem_cell_lookup tegra210_fuse_lookups[] = { + { + .nvmem_name = "fuse", + .cell_name = "tsensor-cpu1", + .dev_id = "700e2000.thermal-sensor", + .con_id = "cpu1", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-cpu2", + .dev_id = "700e2000.thermal-sensor", + .con_id = "cpu2", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-cpu0", + .dev_id = "700e2000.thermal-sensor", + .con_id = "cpu0", + }, { + .nvmem_name = "fuse", + .cell_name = "xusb-pad-calibration", + .dev_id = "7009f000.padctl", + .con_id = "calibration", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-cpu3", + .dev_id = "700e2000.thermal-sensor", + .con_id = "cpu3", + }, { + .nvmem_name = "fuse", + .cell_name = "sata-calibration", + .dev_id = "70020000.sata", + .con_id = "calibration", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-gpu", + .dev_id = "700e2000.thermal-sensor", + .con_id = "gpu", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-mem0", + .dev_id = "700e2000.thermal-sensor", + .con_id = "mem0", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-mem1", + .dev_id = "700e2000.thermal-sensor", + .con_id = "mem1", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-pllx", + .dev_id = "700e2000.thermal-sensor", + .con_id = "pllx", + }, { + .nvmem_name = "fuse", + .cell_name = "tsensor-common", + .dev_id = "700e2000.thermal-sensor", + .con_id = "common", + }, { + .nvmem_name = "fuse", + .cell_name = "gpu-calibration", + .dev_id = "57000000.gpu", + .con_id = "calibration", + }, { + .nvmem_name = "fuse", + .cell_name = "xusb-pad-calibration-ext", + .dev_id = "7009f000.padctl", + .con_id = "calibration-ext", + }, +}; + static const struct tegra_fuse_info tegra210_fuse_info = { .read = tegra30_fuse_read, .size = 0x300, @@ -151,10 +287,26 @@ const struct tegra_fuse_soc tegra210_fuse_soc = { .init = tegra30_fuse_init, .speedo_init = tegra210_init_speedo_data, .info = &tegra210_fuse_info, + .lookups = tegra210_fuse_lookups, + .num_lookups = ARRAY_SIZE(tegra210_fuse_lookups), }; #endif #if defined(CONFIG_ARCH_TEGRA_186_SOC) +static const struct nvmem_cell_lookup tegra186_fuse_lookups[] = { + { + .nvmem_name = "fuse", + .cell_name = "xusb-pad-calibration", + .dev_id = "3520000.padctl", + .con_id = "calibration", + }, { + .nvmem_name = "fuse", + .cell_name = "xusb-pad-calibration-ext", + .dev_id = "3520000.padctl", + .con_id = "calibration-ext", + }, +}; + static const struct tegra_fuse_info tegra186_fuse_info = { .read = tegra30_fuse_read, .size = 0x300, @@ -164,5 +316,7 @@ static const struct tegra_fuse_info tegra186_fuse_info = { const struct tegra_fuse_soc tegra186_fuse_soc = { .init = tegra30_fuse_init, .info = &tegra186_fuse_info, + .lookups = tegra186_fuse_lookups, + .num_lookups = ARRAY_SIZE(tegra186_fuse_lookups), }; #endif diff --git a/drivers/soc/tegra/fuse/fuse.h b/drivers/soc/tegra/fuse/fuse.h index 32bf6c070ae7..0f74c2c34af0 100644 --- a/drivers/soc/tegra/fuse/fuse.h +++ b/drivers/soc/tegra/fuse/fuse.h @@ -13,6 +13,7 @@ #include #include +struct nvmem_cell_lookup; struct nvmem_device; struct tegra_fuse; @@ -28,6 +29,9 @@ struct tegra_fuse_soc { int (*probe)(struct tegra_fuse *fuse); const struct tegra_fuse_info *info; + + const struct nvmem_cell_lookup *lookups; + unsigned int num_lookups; }; struct tegra_fuse { @@ -51,6 +55,7 @@ struct tegra_fuse { } apbdma; struct nvmem_device *nvmem; + struct nvmem_cell_lookup *lookups; }; void tegra_init_revision(void); -- cgit From aba19827fced3f32bd17885db59d27538b0bd223 Mon Sep 17 00:00:00 2001 From: Sowjanya Komatineni Date: Fri, 16 Aug 2019 12:42:01 -0700 Subject: soc/tegra: pmc: Support wake events on more Tegra SoCs This patch allows to create separate irq_set_wake and irq_set_type implementations for different Tegra designs PMC that has different wake models which require difference wake registers and different programming sequence. AOWAKE model support is available for Tegra186 and Tegra194 only and it resides within PMC and supports tiered wake architecture. Tegra210 and prior Tegra designs uses PMC directly to receive wake events and coordinate the wake sequence. Reviewed-by: Dmitry Osipenko Signed-off-by: Sowjanya Komatineni Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 0447afa970f5..63e26bbaf576 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -226,6 +226,8 @@ struct tegra_pmc_soc { void (*setup_irq_polarity)(struct tegra_pmc *pmc, struct device_node *np, bool invert); + int (*irq_set_wake)(struct irq_data *data, unsigned int on); + int (*irq_set_type)(struct irq_data *data, unsigned int type); const char * const *reset_sources; unsigned int num_reset_sources; @@ -1946,7 +1948,7 @@ static const struct irq_domain_ops tegra_pmc_irq_domain_ops = { .alloc = tegra_pmc_irq_alloc, }; -static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on) +static int tegra186_pmc_irq_set_wake(struct irq_data *data, unsigned int on) { struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data); unsigned int offset, bit; @@ -1978,7 +1980,7 @@ static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on) return 0; } -static int tegra_pmc_irq_set_type(struct irq_data *data, unsigned int type) +static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type) { struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data); u32 value; @@ -2032,8 +2034,8 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc) pmc->irq.irq_unmask = irq_chip_unmask_parent; pmc->irq.irq_eoi = irq_chip_eoi_parent; pmc->irq.irq_set_affinity = irq_chip_set_affinity_parent; - pmc->irq.irq_set_type = tegra_pmc_irq_set_type; - pmc->irq.irq_set_wake = tegra_pmc_irq_set_wake; + pmc->irq.irq_set_type = pmc->soc->irq_set_type; + pmc->irq.irq_set_wake = pmc->soc->irq_set_wake; pmc->domain = irq_domain_add_hierarchy(parent, 0, 96, pmc->dev->of_node, &tegra_pmc_irq_domain_ops, pmc); @@ -2706,6 +2708,8 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = { .regs = &tegra186_pmc_regs, .init = NULL, .setup_irq_polarity = tegra186_pmc_setup_irq_polarity, + .irq_set_wake = tegra186_pmc_irq_set_wake, + .irq_set_type = tegra186_pmc_irq_set_type, .reset_sources = tegra186_reset_sources, .num_reset_sources = ARRAY_SIZE(tegra186_reset_sources), .reset_levels = tegra186_reset_levels, -- cgit From 7e9ae849eb1ea4617a9c7229a78c622a214283f2 Mon Sep 17 00:00:00 2001 From: Sowjanya Komatineni Date: Fri, 16 Aug 2019 12:42:02 -0700 Subject: soc/tegra: pmc: Add wake event support on Tegra210 This patch implements PMC wakeup sequence for Tegra210 and defines the commonly used RTC alarm wake event. Signed-off-by: Sowjanya Komatineni Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 63e26bbaf576..e1f14098bd2d 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -58,6 +58,11 @@ #define PMC_CNTRL_SYSCLK_POLARITY BIT(10) /* sys clk polarity */ #define PMC_CNTRL_MAIN_RST BIT(4) +#define PMC_WAKE_MASK 0x0c +#define PMC_WAKE_LEVEL 0x10 +#define PMC_WAKE_STATUS 0x14 +#define PMC_SW_WAKE_STATUS 0x18 + #define DPD_SAMPLE 0x020 #define DPD_SAMPLE_ENABLE BIT(0) #define DPD_SAMPLE_DISABLE (0 << 0) @@ -87,6 +92,11 @@ #define PMC_SCRATCH41 0x140 +#define PMC_WAKE2_MASK 0x160 +#define PMC_WAKE2_LEVEL 0x164 +#define PMC_WAKE2_STATUS 0x168 +#define PMC_SW_WAKE2_STATUS 0x16c + #define PMC_SENSOR_CTRL 0x1b0 #define PMC_SENSOR_CTRL_SCRATCH_WRITE BIT(2) #define PMC_SENSOR_CTRL_ENABLE_RST BIT(1) @@ -1948,6 +1958,86 @@ static const struct irq_domain_ops tegra_pmc_irq_domain_ops = { .alloc = tegra_pmc_irq_alloc, }; +static int tegra210_pmc_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data); + unsigned int offset, bit; + u32 value; + + if (data->hwirq == ULONG_MAX) + return 0; + + offset = data->hwirq / 32; + bit = data->hwirq % 32; + + /* clear wake status */ + tegra_pmc_writel(pmc, 0, PMC_SW_WAKE_STATUS); + tegra_pmc_writel(pmc, 0, PMC_SW_WAKE2_STATUS); + + tegra_pmc_writel(pmc, 0, PMC_WAKE_STATUS); + tegra_pmc_writel(pmc, 0, PMC_WAKE2_STATUS); + + /* enable PMC wake */ + if (data->hwirq >= 32) + offset = PMC_WAKE2_MASK; + else + offset = PMC_WAKE_MASK; + + value = tegra_pmc_readl(pmc, offset); + + if (on) + value |= BIT(bit); + else + value &= ~BIT(bit); + + tegra_pmc_writel(pmc, value, offset); + + return 0; +} + +static int tegra210_pmc_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data); + unsigned int offset, bit; + u32 value; + + if (data->hwirq == ULONG_MAX) + return 0; + + offset = data->hwirq / 32; + bit = data->hwirq % 32; + + if (data->hwirq >= 32) + offset = PMC_WAKE2_LEVEL; + else + offset = PMC_WAKE_LEVEL; + + value = tegra_pmc_readl(pmc, offset); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + case IRQ_TYPE_LEVEL_HIGH: + value |= BIT(bit); + break; + + case IRQ_TYPE_EDGE_FALLING: + case IRQ_TYPE_LEVEL_LOW: + value &= ~BIT(bit); + break; + + case IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING: + value ^= BIT(bit); + break; + + default: + return -EINVAL; + } + + tegra_pmc_writel(pmc, value, offset); + + return 0; +} + static int tegra186_pmc_irq_set_wake(struct irq_data *data, unsigned int on) { struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data); @@ -2566,6 +2656,10 @@ static const struct pinctrl_pin_desc tegra210_pin_descs[] = { TEGRA210_IO_PAD_TABLE(TEGRA_IO_PIN_DESC) }; +static const struct tegra_wake_event tegra210_wake_events[] = { + TEGRA_WAKE_IRQ("rtc", 16, 2), +}; + static const struct tegra_pmc_soc tegra210_pmc_soc = { .num_powergates = ARRAY_SIZE(tegra210_powergates), .powergates = tegra210_powergates, @@ -2583,10 +2677,14 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = { .regs = &tegra20_pmc_regs, .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, + .irq_set_wake = tegra210_pmc_irq_set_wake, + .irq_set_type = tegra210_pmc_irq_set_type, .reset_sources = tegra210_reset_sources, .num_reset_sources = ARRAY_SIZE(tegra210_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, + .num_wake_events = ARRAY_SIZE(tegra210_wake_events), + .wake_events = tegra210_wake_events, }; #define TEGRA186_IO_PAD_TABLE(_pad) \ -- cgit From 455271d9dc5f4cce3d35c5819f8f01c723bca94c Mon Sep 17 00:00:00 2001 From: Sowjanya Komatineni Date: Fri, 16 Aug 2019 12:42:04 -0700 Subject: soc/tegra: pmc: Configure core power request polarity This patch configures polarity of the core power request signal in PMC control register based on the device tree property. PMC asserts and de-asserts power request signal based on it polarity when it need to power-up and power-down the core rail during SC7. Reviewed-by: Dmitry Osipenko Signed-off-by: Sowjanya Komatineni Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index e1f14098bd2d..41e974cd91fe 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -56,6 +56,7 @@ #define PMC_CNTRL_SIDE_EFFECT_LP0 BIT(14) /* LP0 when CPU pwr gated */ #define PMC_CNTRL_SYSCLK_OE BIT(11) /* system clock enable */ #define PMC_CNTRL_SYSCLK_POLARITY BIT(10) /* sys clk polarity */ +#define PMC_CNTRL_PWRREQ_POLARITY BIT(8) #define PMC_CNTRL_MAIN_RST BIT(4) #define PMC_WAKE_MASK 0x0c @@ -2316,6 +2317,11 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc) else value |= PMC_CNTRL_SYSCLK_POLARITY; + if (pmc->corereq_high) + value &= ~PMC_CNTRL_PWRREQ_POLARITY; + else + value |= PMC_CNTRL_PWRREQ_POLARITY; + /* configure the output polarity while the request is tristated */ tegra_pmc_writel(pmc, value, PMC_CNTRL); -- cgit From c7ccfccabb0f819eae1a191ccd94269f577e4523 Mon Sep 17 00:00:00 2001 From: Sowjanya Komatineni Date: Fri, 16 Aug 2019 12:42:05 -0700 Subject: soc/tegra: pmc: Configure deep sleep control settings Tegra210 and prior Tegra chips have deep sleep entry and wakeup related timings which are platform specific that should be configured before entering into deep sleep. Below are the timing specific configurations for deep sleep entry and wakeup. - Core rail power-on stabilization timer - OSC clock stabilization timer after SOC rail power is stabilized. - Core power off time is the minimum wake delay to keep the system in deep sleep state irrespective of any quick wake event. These values depends on the discharge time of regulators and turn OFF time of the PMIC to allow the complete system to finish entering into deep sleep state. These values vary based on the platform design and are specified through the device tree. This patch has implementation to configure these timings which are must to have for proper deep sleep and wakeup operations. Signed-off-by: Sowjanya Komatineni Reviewed-by: Dmitry Osipenko Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 41e974cd91fe..c9b1baae5d8a 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -88,6 +88,8 @@ #define PMC_CPUPWRGOOD_TIMER 0xc8 #define PMC_CPUPWROFF_TIMER 0xcc +#define PMC_COREPWRGOOD_TIMER 0x3c +#define PMC_COREPWROFF_TIMER 0xe0 #define PMC_PWR_DET_VALUE 0xe4 @@ -2303,7 +2305,7 @@ static const struct tegra_pmc_regs tegra20_pmc_regs = { static void tegra20_pmc_init(struct tegra_pmc *pmc) { - u32 value; + u32 value, osc, pmu, off; /* Always enable CPU power request */ value = tegra_pmc_readl(pmc, PMC_CNTRL); @@ -2329,6 +2331,16 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc) value = tegra_pmc_readl(pmc, PMC_CNTRL); value |= PMC_CNTRL_SYSCLK_OE; tegra_pmc_writel(pmc, value, PMC_CNTRL); + + /* program core timings which are applicable only for suspend state */ + if (pmc->suspend_mode != TEGRA_SUSPEND_NONE) { + osc = DIV_ROUND_UP(pmc->core_osc_time * 8192, 1000000); + pmu = DIV_ROUND_UP(pmc->core_pmu_time * 32768, 1000000); + off = DIV_ROUND_UP(pmc->core_off_time * 32768, 1000000); + tegra_pmc_writel(pmc, ((osc << 8) & 0xff00) | (pmu & 0xff), + PMC_COREPWRGOOD_TIMER); + tegra_pmc_writel(pmc, off, PMC_COREPWROFF_TIMER); + } } static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc, -- cgit From 496747e7d907b01bd2507d61bdd6874b987c9629 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Thu, 25 Jul 2019 18:18:31 +0300 Subject: soc/tegra: regulators: Add regulators coupler for Tegra20 Add regulators coupler for Tegra20 SoCs that performs voltage balancing of a coupled regulators and thus provides voltage scaling functionality. There are 3 coupled regulators on all Tegra20 SoCs: CORE, RTC and CPU. The CORE and RTC voltages shall be in range of 170mV from each other and they both shall be higher than the CPU voltage by at least 120mV. This sounds like it could be handle by a generic voltage balancer, but the CORE voltage scaling isn't implemented in any of the upstream drivers yet. It will take quite some time and effort to hook up voltage scaling for all of the drivers, hence we will use a custom coupler that will manage the CPU voltage scaling for the starter. Signed-off-by: Dmitry Osipenko Acked-By: Peter De Schrijver Signed-off-by: Thierry Reding --- drivers/soc/tegra/Kconfig | 5 + drivers/soc/tegra/Makefile | 1 + drivers/soc/tegra/regulators-tegra20.c | 365 +++++++++++++++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 drivers/soc/tegra/regulators-tegra20.c (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig index c8ef05d6b8c7..437466aa57e1 100644 --- a/drivers/soc/tegra/Kconfig +++ b/drivers/soc/tegra/Kconfig @@ -15,6 +15,7 @@ config ARCH_TEGRA_2x_SOC select PL310_ERRATA_769419 if CACHE_L2X0 select SOC_TEGRA_FLOWCTRL select SOC_TEGRA_PMC + select SOC_TEGRA20_VOLTAGE_COUPLER select TEGRA_TIMER help Support for NVIDIA Tegra AP20 and T20 processors, based on the @@ -135,3 +136,7 @@ config SOC_TEGRA_POWERGATE_BPMP def_bool y depends on PM_GENERIC_DOMAINS depends on TEGRA_BPMP + +config SOC_TEGRA20_VOLTAGE_COUPLER + bool "Voltage scaling support for Tegra20 SoCs" + depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile index 902759fe5f4d..9f0bdd53bef8 100644 --- a/drivers/soc/tegra/Makefile +++ b/drivers/soc/tegra/Makefile @@ -5,3 +5,4 @@ obj-y += common.o obj-$(CONFIG_SOC_TEGRA_FLOWCTRL) += flowctrl.o 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 diff --git a/drivers/soc/tegra/regulators-tegra20.c b/drivers/soc/tegra/regulators-tegra20.c new file mode 100644 index 000000000000..ea0eede48802 --- /dev/null +++ b/drivers/soc/tegra/regulators-tegra20.c @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Voltage regulators coupler for NVIDIA Tegra20 + * Copyright (C) 2019 GRATE-DRIVER project + * + * Voltage constraints borrowed from downstream kernel sources + * Copyright (C) 2010-2011 NVIDIA Corporation + */ + +#define pr_fmt(fmt) "tegra voltage-coupler: " fmt + +#include +#include +#include +#include +#include +#include + +struct tegra_regulator_coupler { + struct regulator_coupler coupler; + struct regulator_dev *core_rdev; + struct regulator_dev *cpu_rdev; + struct regulator_dev *rtc_rdev; + int core_min_uV; +}; + +static inline struct tegra_regulator_coupler * +to_tegra_coupler(struct regulator_coupler *coupler) +{ + return container_of(coupler, struct tegra_regulator_coupler, coupler); +} + +static int tegra20_core_limit(struct tegra_regulator_coupler *tegra, + struct regulator_dev *core_rdev) +{ + int core_min_uV = 0; + int core_max_uV; + int core_cur_uV; + int err; + + if (tegra->core_min_uV > 0) + return tegra->core_min_uV; + + core_cur_uV = regulator_get_voltage_rdev(core_rdev); + if (core_cur_uV < 0) + return core_cur_uV; + + core_max_uV = max(core_cur_uV, 1200000); + + err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV); + if (err) + return err; + + /* + * Limit minimum CORE voltage to a value left from bootloader or, + * if it's unreasonably low value, to the most common 1.2v or to + * whatever maximum value defined via board's device-tree. + */ + tegra->core_min_uV = core_max_uV; + + pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV); + + return tegra->core_min_uV; +} + +static int tegra20_core_rtc_max_spread(struct regulator_dev *core_rdev, + struct regulator_dev *rtc_rdev) +{ + struct coupling_desc *c_desc = &core_rdev->coupling_desc; + struct regulator_dev *rdev; + int max_spread; + unsigned int i; + + for (i = 1; i < c_desc->n_coupled; i++) { + max_spread = core_rdev->constraints->max_spread[i - 1]; + rdev = c_desc->coupled_rdevs[i]; + + if (rdev == rtc_rdev && max_spread) + return max_spread; + } + + pr_err_once("rtc-core max-spread is undefined in device-tree\n"); + + return 150000; +} + +static int tegra20_core_rtc_update(struct tegra_regulator_coupler *tegra, + struct regulator_dev *core_rdev, + struct regulator_dev *rtc_rdev, + int cpu_uV, int cpu_min_uV) +{ + int core_min_uV, core_max_uV = INT_MAX; + int rtc_min_uV, rtc_max_uV = INT_MAX; + int core_target_uV; + int rtc_target_uV; + int max_spread; + int core_uV; + int rtc_uV; + int err; + + /* + * RTC and CORE voltages should be no more than 170mV from each other, + * CPU should be below RTC and CORE by at least 120mV. This applies + * to all Tegra20 SoC's. + */ + max_spread = tegra20_core_rtc_max_spread(core_rdev, rtc_rdev); + + /* + * The core voltage scaling is currently not hooked up in drivers, + * hence we will limit the minimum core voltage to a reasonable value. + * This should be good enough for the time being. + */ + core_min_uV = tegra20_core_limit(tegra, core_rdev); + if (core_min_uV < 0) + return core_min_uV; + + err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV); + if (err) + return err; + + err = regulator_check_consumers(core_rdev, &core_min_uV, &core_max_uV, + PM_SUSPEND_ON); + if (err) + return err; + + core_uV = regulator_get_voltage_rdev(core_rdev); + if (core_uV < 0) + return core_uV; + + core_min_uV = max(cpu_min_uV + 125000, core_min_uV); + if (core_min_uV > core_max_uV) + return -EINVAL; + + if (cpu_uV + 120000 > core_uV) + pr_err("core-cpu voltage constraint violated: %d %d\n", + core_uV, cpu_uV + 120000); + + rtc_uV = regulator_get_voltage_rdev(rtc_rdev); + if (rtc_uV < 0) + return rtc_uV; + + if (cpu_uV + 120000 > rtc_uV) + pr_err("rtc-cpu voltage constraint violated: %d %d\n", + rtc_uV, cpu_uV + 120000); + + if (abs(core_uV - rtc_uV) > 170000) + pr_err("core-rtc voltage constraint violated: %d %d\n", + core_uV, rtc_uV); + + rtc_min_uV = max(cpu_min_uV + 125000, core_min_uV - max_spread); + + err = regulator_check_voltage(rtc_rdev, &rtc_min_uV, &rtc_max_uV); + if (err) + return err; + + while (core_uV != core_min_uV || rtc_uV != rtc_min_uV) { + if (core_uV < core_min_uV) { + core_target_uV = min(core_uV + max_spread, core_min_uV); + core_target_uV = min(rtc_uV + max_spread, core_target_uV); + } else { + core_target_uV = max(core_uV - max_spread, core_min_uV); + core_target_uV = max(rtc_uV - max_spread, core_target_uV); + } + + err = regulator_set_voltage_rdev(core_rdev, + core_target_uV, + core_max_uV, + PM_SUSPEND_ON); + if (err) + return err; + + core_uV = core_target_uV; + + if (rtc_uV < rtc_min_uV) { + rtc_target_uV = min(rtc_uV + max_spread, rtc_min_uV); + rtc_target_uV = min(core_uV + max_spread, rtc_target_uV); + } else { + rtc_target_uV = max(rtc_uV - max_spread, rtc_min_uV); + rtc_target_uV = max(core_uV - max_spread, rtc_target_uV); + } + + err = regulator_set_voltage_rdev(rtc_rdev, + rtc_target_uV, + rtc_max_uV, + PM_SUSPEND_ON); + if (err) + return err; + + rtc_uV = rtc_target_uV; + } + + return 0; +} + +static int tegra20_core_voltage_update(struct tegra_regulator_coupler *tegra, + struct regulator_dev *cpu_rdev, + struct regulator_dev *core_rdev, + struct regulator_dev *rtc_rdev) +{ + int cpu_uV; + + cpu_uV = regulator_get_voltage_rdev(cpu_rdev); + if (cpu_uV < 0) + return cpu_uV; + + return tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev, + cpu_uV, cpu_uV); +} + +static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra, + struct regulator_dev *cpu_rdev, + struct regulator_dev *core_rdev, + struct regulator_dev *rtc_rdev) +{ + int cpu_min_uV_consumers = 0; + int cpu_max_uV = INT_MAX; + int cpu_min_uV = 0; + int cpu_uV; + int err; + + err = regulator_check_voltage(cpu_rdev, &cpu_min_uV, &cpu_max_uV); + if (err) + return err; + + err = regulator_check_consumers(cpu_rdev, &cpu_min_uV, &cpu_max_uV, + PM_SUSPEND_ON); + if (err) + return err; + + err = regulator_check_consumers(cpu_rdev, &cpu_min_uV_consumers, + &cpu_max_uV, PM_SUSPEND_ON); + if (err) + return err; + + cpu_uV = regulator_get_voltage_rdev(cpu_rdev); + if (cpu_uV < 0) + return cpu_uV; + + /* + * CPU's regulator may not have any consumers, hence the voltage + * must not be changed in that case because CPU simply won't + * survive the voltage drop if it's running on a higher frequency. + */ + if (!cpu_min_uV_consumers) + cpu_min_uV = cpu_uV; + + if (cpu_min_uV > cpu_uV) { + err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev, + cpu_uV, cpu_min_uV); + if (err) + return err; + + err = regulator_set_voltage_rdev(cpu_rdev, cpu_min_uV, + cpu_max_uV, PM_SUSPEND_ON); + if (err) + return err; + } else if (cpu_min_uV < cpu_uV) { + err = regulator_set_voltage_rdev(cpu_rdev, cpu_min_uV, + cpu_max_uV, PM_SUSPEND_ON); + if (err) + return err; + + err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev, + cpu_uV, cpu_min_uV); + if (err) + return err; + } + + return 0; +} + +static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler, + struct regulator_dev *rdev, + suspend_state_t state) +{ + struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler); + struct regulator_dev *core_rdev = tegra->core_rdev; + struct regulator_dev *cpu_rdev = tegra->cpu_rdev; + struct regulator_dev *rtc_rdev = tegra->rtc_rdev; + + if ((core_rdev != rdev && cpu_rdev != rdev && rtc_rdev != rdev) || + state != PM_SUSPEND_ON) { + pr_err("regulators are not coupled properly\n"); + return -EINVAL; + } + + if (rdev == cpu_rdev) + return tegra20_cpu_voltage_update(tegra, cpu_rdev, + core_rdev, rtc_rdev); + + if (rdev == core_rdev) + return tegra20_core_voltage_update(tegra, cpu_rdev, + core_rdev, rtc_rdev); + + pr_err("changing %s voltage not permitted\n", rdev_get_name(rtc_rdev)); + + return -EPERM; +} + +static int tegra20_regulator_attach(struct regulator_coupler *coupler, + struct regulator_dev *rdev) +{ + struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler); + struct device_node *np = rdev->dev.of_node; + + if (of_property_read_bool(np, "nvidia,tegra-core-regulator") && + !tegra->core_rdev) { + tegra->core_rdev = rdev; + return 0; + } + + if (of_property_read_bool(np, "nvidia,tegra-rtc-regulator") && + !tegra->rtc_rdev) { + tegra->rtc_rdev = rdev; + return 0; + } + + if (of_property_read_bool(np, "nvidia,tegra-cpu-regulator") && + !tegra->cpu_rdev) { + tegra->cpu_rdev = rdev; + return 0; + } + + return -EINVAL; +} + +static int tegra20_regulator_detach(struct regulator_coupler *coupler, + struct regulator_dev *rdev) +{ + struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler); + + if (tegra->core_rdev == rdev) { + tegra->core_rdev = NULL; + return 0; + } + + if (tegra->rtc_rdev == rdev) { + tegra->rtc_rdev = NULL; + return 0; + } + + if (tegra->cpu_rdev == rdev) { + tegra->cpu_rdev = NULL; + return 0; + } + + return -EINVAL; +} + +static struct tegra_regulator_coupler tegra20_coupler = { + .coupler = { + .attach_regulator = tegra20_regulator_attach, + .detach_regulator = tegra20_regulator_detach, + .balance_voltage = tegra20_regulator_balance_voltage, + }, +}; + +static int __init tegra_regulator_coupler_init(void) +{ + if (!of_machine_is_compatible("nvidia,tegra20")) + return 0; + + return regulator_coupler_register(&tegra20_coupler.coupler); +} +arch_initcall(tegra_regulator_coupler_init); -- cgit From 783807436f363e5b1ad4d43ba7debbedfcadbb99 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Thu, 25 Jul 2019 18:18:32 +0300 Subject: soc/tegra: regulators: Add regulators coupler for Tegra30 Add regulators coupler for Tegra30 SoCs that performs voltage balancing of a coupled regulators and thus provides voltage scaling functionality. There are 2 coupled regulators on all Tegra30 SoCs: CORE and CPU. The coupled regulator voltages shall be in a range of 300mV from each other and CORE voltage shall be higher than the CPU by N mV, where N depends on the CPU voltage. Signed-off-by: Dmitry Osipenko Acked-By: Peter De Schrijver Signed-off-by: Thierry Reding --- drivers/soc/tegra/Kconfig | 5 + drivers/soc/tegra/Makefile | 1 + drivers/soc/tegra/regulators-tegra30.c | 317 +++++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 drivers/soc/tegra/regulators-tegra30.c (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig index 437466aa57e1..84bd615c4a92 100644 --- a/drivers/soc/tegra/Kconfig +++ b/drivers/soc/tegra/Kconfig @@ -29,6 +29,7 @@ config ARCH_TEGRA_3x_SOC select PL310_ERRATA_769419 if CACHE_L2X0 select SOC_TEGRA_FLOWCTRL select SOC_TEGRA_PMC + select SOC_TEGRA30_VOLTAGE_COUPLER select TEGRA_TIMER help Support for NVIDIA Tegra T30 processor family, based on the @@ -140,3 +141,7 @@ config SOC_TEGRA_POWERGATE_BPMP config SOC_TEGRA20_VOLTAGE_COUPLER bool "Voltage scaling support for Tegra20 SoCs" depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST + +config SOC_TEGRA30_VOLTAGE_COUPLER + bool "Voltage scaling support for Tegra30 SoCs" + depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile index 9f0bdd53bef8..9c809c1814bd 100644 --- a/drivers/soc/tegra/Makefile +++ b/drivers/soc/tegra/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_SOC_TEGRA_FLOWCTRL) += flowctrl.o 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 diff --git a/drivers/soc/tegra/regulators-tegra30.c b/drivers/soc/tegra/regulators-tegra30.c new file mode 100644 index 000000000000..8e623ff18e70 --- /dev/null +++ b/drivers/soc/tegra/regulators-tegra30.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Voltage regulators coupler for NVIDIA Tegra30 + * Copyright (C) 2019 GRATE-DRIVER project + * + * Voltage constraints borrowed from downstream kernel sources + * Copyright (C) 2010-2011 NVIDIA Corporation + */ + +#define pr_fmt(fmt) "tegra voltage-coupler: " fmt + +#include +#include +#include +#include +#include +#include + +#include + +struct tegra_regulator_coupler { + struct regulator_coupler coupler; + struct regulator_dev *core_rdev; + struct regulator_dev *cpu_rdev; + int core_min_uV; +}; + +static inline struct tegra_regulator_coupler * +to_tegra_coupler(struct regulator_coupler *coupler) +{ + return container_of(coupler, struct tegra_regulator_coupler, coupler); +} + +static int tegra30_core_limit(struct tegra_regulator_coupler *tegra, + struct regulator_dev *core_rdev) +{ + int core_min_uV = 0; + int core_max_uV; + int core_cur_uV; + int err; + + if (tegra->core_min_uV > 0) + return tegra->core_min_uV; + + core_cur_uV = regulator_get_voltage_rdev(core_rdev); + if (core_cur_uV < 0) + return core_cur_uV; + + core_max_uV = max(core_cur_uV, 1200000); + + err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV); + if (err) + return err; + + /* + * Limit minimum CORE voltage to a value left from bootloader or, + * if it's unreasonably low value, to the most common 1.2v or to + * whatever maximum value defined via board's device-tree. + */ + tegra->core_min_uV = core_max_uV; + + pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV); + + return tegra->core_min_uV; +} + +static int tegra30_core_cpu_limit(int cpu_uV) +{ + if (cpu_uV < 800000) + return 950000; + + if (cpu_uV < 900000) + return 1000000; + + if (cpu_uV < 1000000) + return 1100000; + + if (cpu_uV < 1100000) + return 1200000; + + if (cpu_uV < 1250000) { + switch (tegra_sku_info.cpu_speedo_id) { + case 0 ... 1: + case 4: + case 7 ... 8: + return 1200000; + + default: + return 1300000; + } + } + + return -EINVAL; +} + +static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra, + struct regulator_dev *cpu_rdev, + struct regulator_dev *core_rdev) +{ + int core_min_uV, core_max_uV = INT_MAX; + int cpu_min_uV, cpu_max_uV = INT_MAX; + int cpu_min_uV_consumers = 0; + int core_min_limited_uV; + int core_target_uV; + int cpu_target_uV; + int core_max_step; + int cpu_max_step; + int max_spread; + int core_uV; + int cpu_uV; + int err; + + /* + * CPU voltage should not got lower than 300mV from the CORE. + * CPU voltage should stay below the CORE by 100mV+, depending + * by the CORE voltage. This applies to all Tegra30 SoC's. + */ + max_spread = cpu_rdev->constraints->max_spread[0]; + cpu_max_step = cpu_rdev->constraints->max_uV_step; + core_max_step = core_rdev->constraints->max_uV_step; + + if (!max_spread) { + pr_err_once("cpu-core max-spread is undefined in device-tree\n"); + max_spread = 300000; + } + + if (!cpu_max_step) { + pr_err_once("cpu max-step is undefined in device-tree\n"); + cpu_max_step = 150000; + } + + if (!core_max_step) { + pr_err_once("core max-step is undefined in device-tree\n"); + core_max_step = 150000; + } + + /* + * The CORE voltage scaling is currently not hooked up in drivers, + * hence we will limit the minimum CORE voltage to a reasonable value. + * This should be good enough for the time being. + */ + core_min_uV = tegra30_core_limit(tegra, core_rdev); + if (core_min_uV < 0) + return core_min_uV; + + err = regulator_check_consumers(core_rdev, &core_min_uV, &core_max_uV, + PM_SUSPEND_ON); + if (err) + return err; + + core_uV = regulator_get_voltage_rdev(core_rdev); + if (core_uV < 0) + return core_uV; + + cpu_min_uV = core_min_uV - max_spread; + + err = regulator_check_consumers(cpu_rdev, &cpu_min_uV, &cpu_max_uV, + PM_SUSPEND_ON); + if (err) + return err; + + err = regulator_check_consumers(cpu_rdev, &cpu_min_uV_consumers, + &cpu_max_uV, PM_SUSPEND_ON); + if (err) + return err; + + err = regulator_check_voltage(cpu_rdev, &cpu_min_uV, &cpu_max_uV); + if (err) + return err; + + cpu_uV = regulator_get_voltage_rdev(cpu_rdev); + if (cpu_uV < 0) + return cpu_uV; + + /* + * CPU's regulator may not have any consumers, hence the voltage + * must not be changed in that case because CPU simply won't + * survive the voltage drop if it's running on a higher frequency. + */ + if (!cpu_min_uV_consumers) + cpu_min_uV = cpu_uV; + + /* + * Bootloader shall set up voltages correctly, but if it + * happens that there is a violation, then try to fix it + * at first. + */ + core_min_limited_uV = tegra30_core_cpu_limit(cpu_uV); + if (core_min_limited_uV < 0) + return core_min_limited_uV; + + core_min_uV = max(core_min_uV, tegra30_core_cpu_limit(cpu_min_uV)); + + err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV); + if (err) + return err; + + if (core_min_limited_uV > core_uV) { + pr_err("core voltage constraint violated: %d %d %d\n", + core_uV, core_min_limited_uV, cpu_uV); + goto update_core; + } + + while (cpu_uV != cpu_min_uV || core_uV != core_min_uV) { + if (cpu_uV < cpu_min_uV) { + cpu_target_uV = min(cpu_uV + cpu_max_step, cpu_min_uV); + } else { + cpu_target_uV = max(cpu_uV - cpu_max_step, cpu_min_uV); + cpu_target_uV = max(core_uV - max_spread, cpu_target_uV); + } + + err = regulator_set_voltage_rdev(cpu_rdev, + cpu_target_uV, + cpu_max_uV, + PM_SUSPEND_ON); + if (err) + return err; + + cpu_uV = cpu_target_uV; +update_core: + core_min_limited_uV = tegra30_core_cpu_limit(cpu_uV); + if (core_min_limited_uV < 0) + return core_min_limited_uV; + + core_target_uV = max(core_min_limited_uV, core_min_uV); + + if (core_uV < core_target_uV) { + core_target_uV = min(core_target_uV, core_uV + core_max_step); + core_target_uV = min(core_target_uV, cpu_uV + max_spread); + } else { + core_target_uV = max(core_target_uV, core_uV - core_max_step); + } + + err = regulator_set_voltage_rdev(core_rdev, + core_target_uV, + core_max_uV, + PM_SUSPEND_ON); + if (err) + return err; + + core_uV = core_target_uV; + } + + return 0; +} + +static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler, + struct regulator_dev *rdev, + suspend_state_t state) +{ + struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler); + struct regulator_dev *core_rdev = tegra->core_rdev; + struct regulator_dev *cpu_rdev = tegra->cpu_rdev; + + if ((core_rdev != rdev && cpu_rdev != rdev) || state != PM_SUSPEND_ON) { + pr_err("regulators are not coupled properly\n"); + return -EINVAL; + } + + return tegra30_voltage_update(tegra, cpu_rdev, core_rdev); +} + +static int tegra30_regulator_attach(struct regulator_coupler *coupler, + struct regulator_dev *rdev) +{ + struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler); + struct device_node *np = rdev->dev.of_node; + + if (of_property_read_bool(np, "nvidia,tegra-core-regulator") && + !tegra->core_rdev) { + tegra->core_rdev = rdev; + return 0; + } + + if (of_property_read_bool(np, "nvidia,tegra-cpu-regulator") && + !tegra->cpu_rdev) { + tegra->cpu_rdev = rdev; + return 0; + } + + return -EINVAL; +} + +static int tegra30_regulator_detach(struct regulator_coupler *coupler, + struct regulator_dev *rdev) +{ + struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler); + + if (tegra->core_rdev == rdev) { + tegra->core_rdev = NULL; + return 0; + } + + if (tegra->cpu_rdev == rdev) { + tegra->cpu_rdev = NULL; + return 0; + } + + return -EINVAL; +} + +static struct tegra_regulator_coupler tegra30_coupler = { + .coupler = { + .attach_regulator = tegra30_regulator_attach, + .detach_regulator = tegra30_regulator_detach, + .balance_voltage = tegra30_regulator_balance_voltage, + }, +}; + +static int __init tegra_regulator_coupler_init(void) +{ + if (!of_machine_is_compatible("nvidia,tegra30")) + return 0; + + return regulator_coupler_register(&tegra30_coupler.coupler); +} +arch_initcall(tegra_regulator_coupler_init); -- cgit From e57a243f5d896f02510e4be92df0873b4c1aab7f Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Thu, 26 Sep 2019 22:17:54 +0300 Subject: soc/tegra: pmc: Query PCLK clock rate at probe time It is possible to get a lockup if kernel decides to enter LP2 cpuidle from some clk-notifier, in that case CCF's "prepare" mutex is kept locked and thus clk_get_rate(pclk) blocks on the same mutex with interrupts being disabled, hanging machine. Signed-off-by: Dmitry Osipenko Acked-By: Peter De Schrijver Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 74 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 15 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index c9b1baae5d8a..1f78e8497fde 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -324,6 +324,7 @@ static const char * const tegra210_reset_sources[] = { * @pctl_dev: pin controller exposed by the PMC * @domain: IRQ domain provided by the PMC * @irq: chip implementation for the IRQ domain + * @clk_nb: pclk clock changes handler */ struct tegra_pmc { struct device *dev; @@ -359,6 +360,8 @@ struct tegra_pmc { struct irq_domain *domain; struct irq_chip irq; + + struct notifier_block clk_nb; }; static struct tegra_pmc *pmc = &(struct tegra_pmc) { @@ -1207,7 +1210,7 @@ static int tegra_io_pad_prepare(struct tegra_pmc *pmc, enum tegra_io_pad id, return err; if (pmc->clk) { - rate = clk_get_rate(pmc->clk); + rate = pmc->rate; if (!rate) { dev_err(pmc->dev, "failed to get clock rate\n"); return -ENODEV; @@ -1448,6 +1451,7 @@ void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode) void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) { unsigned long long rate = 0; + u64 ticks; u32 value; switch (mode) { @@ -1456,7 +1460,7 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) break; case TEGRA_SUSPEND_LP2: - rate = clk_get_rate(pmc->clk); + rate = pmc->rate; break; default: @@ -1466,21 +1470,15 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) if (WARN_ON_ONCE(rate == 0)) rate = 100000000; - if (rate != pmc->rate) { - u64 ticks; - - ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1; - do_div(ticks, USEC_PER_SEC); - tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER); + ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1; + do_div(ticks, USEC_PER_SEC); + tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER); - ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1; - do_div(ticks, USEC_PER_SEC); - tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER); + ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1; + do_div(ticks, USEC_PER_SEC); + tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER); - wmb(); - - pmc->rate = rate; - } + wmb(); value = tegra_pmc_readl(pmc, PMC_CNTRL); value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; @@ -2140,6 +2138,33 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc) return 0; } +static int tegra_pmc_clk_notify_cb(struct notifier_block *nb, + unsigned long action, void *ptr) +{ + struct tegra_pmc *pmc = container_of(nb, struct tegra_pmc, clk_nb); + struct clk_notifier_data *data = ptr; + + switch (action) { + case PRE_RATE_CHANGE: + mutex_lock(&pmc->powergates_lock); + break; + + case POST_RATE_CHANGE: + pmc->rate = data->new_rate; + /* fall through */ + + case ABORT_RATE_CHANGE: + mutex_unlock(&pmc->powergates_lock); + break; + + default: + WARN_ON_ONCE(1); + return notifier_from_errno(-EINVAL); + } + + return NOTIFY_OK; +} + static int tegra_pmc_probe(struct platform_device *pdev) { void __iomem *base; @@ -2203,6 +2228,23 @@ static int tegra_pmc_probe(struct platform_device *pdev) pmc->clk = NULL; } + /* + * PCLK clock rate can't be retrieved using CLK API because it + * causes lockup if CPU enters LP2 idle state from some other + * CLK notifier, hence we're caching the rate's value locally. + */ + if (pmc->clk) { + pmc->clk_nb.notifier_call = tegra_pmc_clk_notify_cb; + err = clk_notifier_register(pmc->clk, &pmc->clk_nb); + if (err) { + dev_err(&pdev->dev, + "failed to register clk notifier\n"); + return err; + } + + pmc->rate = clk_get_rate(pmc->clk); + } + pmc->dev = &pdev->dev; tegra_pmc_init(pmc); @@ -2254,6 +2296,8 @@ cleanup_debugfs: cleanup_sysfs: device_remove_file(&pdev->dev, &dev_attr_reset_reason); device_remove_file(&pdev->dev, &dev_attr_reset_level); + clk_notifier_unregister(pmc->clk, &pmc->clk_nb); + return err; } -- cgit From 69dfb3d4a89afccca1d8f282e49ad1362100cc43 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Thu, 26 Sep 2019 22:17:55 +0300 Subject: soc/tegra: pmc: Remove unnecessary memory barrier The removed barrier isn't needed because writes/reads are strictly ordered and even if PMC had separate ports for writes, it wouldn't matter since the hardware logic takes into effect after triggering CPU's power-gating and at that point all CPU accesses are guaranteed to be completed. That barrier was copied from the old arch/ code during transition to the soc/ PMC driver and even that the code structure was different back then, the barrier didn't have a real useful purpose from the start. Lastly, the tegra_pmc_writel() naturally inserts wmb() because it uses writel(), and thus this change doesn't actually make any difference in terms of interacting with hardware. Hence let's remove the barrier to clean up code a tad. Signed-off-by: Dmitry Osipenko Signed-off-by: Thierry Reding --- drivers/soc/tegra/pmc.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 1f78e8497fde..8db63cfba833 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -1478,8 +1478,6 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode) do_div(ticks, USEC_PER_SEC); tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER); - wmb(); - value = tegra_pmc_readl(pmc, PMC_CNTRL); value &= ~PMC_CNTRL_SIDE_EFFECT_LP0; value |= PMC_CNTRL_CPU_PWRREQ_OE; -- cgit