302 lines
7.1 KiB
C
302 lines
7.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* DDR Self-Refresh Power Down (SRPD) support for Broadcom STB SoCs
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#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");
|