// SPDX-License-Identifier: GPL-2.0-only /* * DDR Self-Refresh Power Down (SRPD) support for Broadcom STB SoCs * */ #include #include #include #include #include #include #define REG_MEMC_CNTRLR_CONFIG 0x00 #define CNTRLR_CONFIG_LPDDR4_SHIFT 5 #define CNTRLR_CONFIG_MASK 0xf #define REG_MEMC_SRPD_CFG_21 0x20 #define REG_MEMC_SRPD_CFG_20 0x34 #define REG_MEMC_SRPD_CFG_1x 0x3c #define INACT_COUNT_SHIFT 0 #define INACT_COUNT_MASK 0xffff #define SRPD_EN_SHIFT 16 struct brcmstb_memc_data { u32 srpd_offset; }; struct brcmstb_memc { struct device *dev; void __iomem *ddr_ctrl; unsigned int timeout_cycles; u32 frequency; u32 srpd_offset; }; static int brcmstb_memc_uses_lpddr4(struct brcmstb_memc *memc) { void __iomem *config = memc->ddr_ctrl + REG_MEMC_CNTRLR_CONFIG; u32 reg; reg = readl_relaxed(config) & CNTRLR_CONFIG_MASK; return reg == CNTRLR_CONFIG_LPDDR4_SHIFT; } static int brcmstb_memc_srpd_config(struct brcmstb_memc *memc, unsigned int cycles) { void __iomem *cfg = memc->ddr_ctrl + memc->srpd_offset; u32 val; /* Max timeout supported in HW */ if (cycles > INACT_COUNT_MASK) return -EINVAL; memc->timeout_cycles = cycles; val = (cycles << INACT_COUNT_SHIFT) & INACT_COUNT_MASK; if (cycles) val |= BIT(SRPD_EN_SHIFT); writel_relaxed(val, cfg); /* Ensure the write is committed to the controller */ (void)readl_relaxed(cfg); return 0; } static ssize_t frequency_show(struct device *dev, struct device_attribute *attr, char *buf) { struct brcmstb_memc *memc = dev_get_drvdata(dev); return sprintf(buf, "%d\n", memc->frequency); } static ssize_t srpd_show(struct device *dev, struct device_attribute *attr, char *buf) { struct brcmstb_memc *memc = dev_get_drvdata(dev); return sprintf(buf, "%d\n", memc->timeout_cycles); } static ssize_t srpd_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct brcmstb_memc *memc = dev_get_drvdata(dev); unsigned int val; int ret; /* * Cannot change the inactivity timeout on LPDDR4 chips because the * dynamic tuning process will also get affected by the inactivity * timeout, thus making it non functional. */ if (brcmstb_memc_uses_lpddr4(memc)) return -EOPNOTSUPP; ret = kstrtouint(buf, 10, &val); if (ret < 0) return ret; ret = brcmstb_memc_srpd_config(memc, val); if (ret) return ret; return count; } static DEVICE_ATTR_RO(frequency); static DEVICE_ATTR_RW(srpd); static struct attribute *dev_attrs[] = { &dev_attr_frequency.attr, &dev_attr_srpd.attr, NULL, }; static struct attribute_group dev_attr_group = { .attrs = dev_attrs, }; static const struct of_device_id brcmstb_memc_of_match[]; static int brcmstb_memc_probe(struct platform_device *pdev) { const struct brcmstb_memc_data *memc_data; const struct of_device_id *of_id; struct device *dev = &pdev->dev; struct brcmstb_memc *memc; int ret; memc = devm_kzalloc(dev, sizeof(*memc), GFP_KERNEL); if (!memc) return -ENOMEM; dev_set_drvdata(dev, memc); of_id = of_match_device(brcmstb_memc_of_match, dev); memc_data = of_id->data; memc->srpd_offset = memc_data->srpd_offset; memc->ddr_ctrl = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(memc->ddr_ctrl)) return PTR_ERR(memc->ddr_ctrl); of_property_read_u32(pdev->dev.of_node, "clock-frequency", &memc->frequency); ret = sysfs_create_group(&dev->kobj, &dev_attr_group); if (ret) return ret; return 0; } static int brcmstb_memc_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; sysfs_remove_group(&dev->kobj, &dev_attr_group); return 0; } enum brcmstb_memc_hwtype { BRCMSTB_MEMC_V21, BRCMSTB_MEMC_V20, BRCMSTB_MEMC_V1X, }; static const struct brcmstb_memc_data brcmstb_memc_versions[] = { { .srpd_offset = REG_MEMC_SRPD_CFG_21 }, { .srpd_offset = REG_MEMC_SRPD_CFG_20 }, { .srpd_offset = REG_MEMC_SRPD_CFG_1x }, }; static const struct of_device_id brcmstb_memc_of_match[] = { { .compatible = "brcm,brcmstb-memc-ddr-rev-b.1.x", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V1X] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.2.0", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V20] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.2.1", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.2.2", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.2.3", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.2.5", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.2.6", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.2.7", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.2.8", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.3.0", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-b.3.1", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-c.1.0", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-c.1.1", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-c.1.2", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-c.1.3", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, { .compatible = "brcm,brcmstb-memc-ddr-rev-c.1.4", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21] }, /* default to the original offset */ { .compatible = "brcm,brcmstb-memc-ddr", .data = &brcmstb_memc_versions[BRCMSTB_MEMC_V1X] }, {} }; static int brcmstb_memc_suspend(struct device *dev) { struct brcmstb_memc *memc = dev_get_drvdata(dev); void __iomem *cfg = memc->ddr_ctrl + memc->srpd_offset; u32 val; if (memc->timeout_cycles == 0) return 0; /* * Disable SRPD prior to suspending the system since that can * cause issues with other memory clients managed by the ARM * trusted firmware to access memory. */ val = readl_relaxed(cfg); val &= ~BIT(SRPD_EN_SHIFT); writel_relaxed(val, cfg); /* Ensure the write is committed to the controller */ (void)readl_relaxed(cfg); return 0; } static int brcmstb_memc_resume(struct device *dev) { struct brcmstb_memc *memc = dev_get_drvdata(dev); if (memc->timeout_cycles == 0) return 0; return brcmstb_memc_srpd_config(memc, memc->timeout_cycles); } static DEFINE_SIMPLE_DEV_PM_OPS(brcmstb_memc_pm_ops, brcmstb_memc_suspend, brcmstb_memc_resume); static struct platform_driver brcmstb_memc_driver = { .probe = brcmstb_memc_probe, .remove = brcmstb_memc_remove, .driver = { .name = "brcmstb_memc", .of_match_table = brcmstb_memc_of_match, .pm = pm_ptr(&brcmstb_memc_pm_ops), }, }; module_platform_driver(brcmstb_memc_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Broadcom"); MODULE_DESCRIPTION("DDR SRPD driver for Broadcom STB chips");