[PATCH 2/3] soc: sifive: Add SiFive specific Cadence DDR controller driver
Palmer Dabbelt
palmer at dabbelt.com
Tue Aug 25 12:02:50 EDT 2020
On Tue, 25 Aug 2020 05:06:21 PDT (-0700), yash.shah at sifive.com wrote:
> Add a driver to manage the Cadence DDR controller present on SiFive SoCs
> At present the driver manages the EDAC feature of the DDR controller.
> Additional features may be added to the driver in future to control
> other aspects of the DDR controller.
>
> Signed-off-by: Yash Shah <yash.shah at sifive.com>
> ---
> drivers/soc/sifive/Kconfig | 6 ++
> drivers/soc/sifive/Makefile | 3 +-
> drivers/soc/sifive/sifive_ddr.c | 207 ++++++++++++++++++++++++++++++++++++++++
> include/soc/sifive/sifive_ddr.h | 73 ++++++++++++++
> 4 files changed, 288 insertions(+), 1 deletion(-)
> create mode 100644 drivers/soc/sifive/sifive_ddr.c
> create mode 100644 include/soc/sifive/sifive_ddr.h
>
> diff --git a/drivers/soc/sifive/Kconfig b/drivers/soc/sifive/Kconfig
> index 58cf8c4..f41d8fe 100644
> --- a/drivers/soc/sifive/Kconfig
> +++ b/drivers/soc/sifive/Kconfig
> @@ -7,4 +7,10 @@ config SIFIVE_L2
> help
> Support for the L2 cache controller on SiFive platforms.
>
> +config SIFIVE_DDR
> + bool "Sifive DDR controller driver"
> + help
> + Support for the management of cadence DDR controller on SiFive
> + platforms.
> +
> endif
> diff --git a/drivers/soc/sifive/Makefile b/drivers/soc/sifive/Makefile
> index b5caff7..b4acb5c 100644
> --- a/drivers/soc/sifive/Makefile
> +++ b/drivers/soc/sifive/Makefile
> @@ -1,3 +1,4 @@
> # SPDX-License-Identifier: GPL-2.0
>
> -obj-$(CONFIG_SIFIVE_L2) += sifive_l2_cache.o
> +obj-$(CONFIG_SIFIVE_L2) += sifive_l2_cache.o
> +obj-$(CONFIG_SIFIVE_DDR) += sifive_ddr.o
> diff --git a/drivers/soc/sifive/sifive_ddr.c b/drivers/soc/sifive/sifive_ddr.c
> new file mode 100644
> index 0000000..b1b421c
> --- /dev/null
> +++ b/drivers/soc/sifive/sifive_ddr.c
> @@ -0,0 +1,207 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * SiFive specific cadence DDR controller Driver
> + *
> + * Copyright (C) 2019-2020 SiFive, Inc.
> + *
> + */
> +#include <linux/init.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/module.h>
> +#include <linux/uaccess.h>
> +#include <linux/delay.h>
> +#include <soc/sifive/sifive_ddr.h>
> +
> +static ATOMIC_NOTIFIER_HEAD(ddr_err_chain);
> +
> +int register_sifive_ddr_error_notifier(struct notifier_block *nb)
> +{
> + return atomic_notifier_chain_register(&ddr_err_chain, nb);
> +}
> +EXPORT_SYMBOL_GPL(register_sifive_ddr_error_notifier);
> +
> +int unregister_sifive_ddr_error_notifier(struct notifier_block *nb)
> +{
> + return atomic_notifier_chain_unregister(&ddr_err_chain, nb);
> +}
> +EXPORT_SYMBOL_GPL(unregister_sifive_ddr_error_notifier);
> +
> +static void handle_ce(struct sifive_ddr_priv *pv)
> +{
> + u64 err_c_addr = 0x0;
> + u64 err_c_data = 0x0;
> + u32 err_c_synd, err_c_id;
> + u32 sig_val_l, sig_val_h;
> +
> + sig_val_l = readl(pv->reg + ECC_C_ADDR_L_REG);
> + sig_val_h = (readl(pv->reg + ECC_C_ADDR_H_REG) &
> + ECC_ADDR_H_MASK);
> + err_c_addr = (((ulong)sig_val_h << CTL_REG_WIDTH_SHIFT) | sig_val_l);
> +
> + sig_val_l = readl(pv->reg + ECC_C_DATA_L_REG);
> + sig_val_h = readl(pv->reg + ECC_C_DATA_H_REG);
> + err_c_data = (((ulong)sig_val_h << CTL_REG_WIDTH_SHIFT) | sig_val_l);
> +
> + err_c_id = ((readl(pv->reg + ECC_U_C_ID_REG) &
> + ECC_C_ID_MASK) >> ECC_C_ID_SHIFT);
> +
> + err_c_synd = ((readl(pv->reg + ECC_C_SYND_REG) &
> + ECC_SYND_MASK) >> ECC_SYND_SHIFT);
> +
> + pv->error_count = 1;
> + pv->page_frame_number = err_c_addr >> PAGE_SHIFT;
> + pv->offset_in_page = err_c_addr & ~PAGE_MASK;
> + pv->syndrome = err_c_synd;
> + pv->top_layer = 0;
> + pv->mid_layer = 0;
> + pv->low_layer = -1;
> +
> + atomic_notifier_call_chain(&ddr_err_chain, SIFIVE_DDR_ERR_TYPE_CE, pv);
> +}
> +
> +static void handle_ue(struct sifive_ddr_priv *pv)
> +{
> + u64 err_u_addr = 0x0;
> + u64 err_u_data = 0x0;
> + u32 err_u_synd, err_u_id;
> + u32 sig_val_l, sig_val_h;
> +
> + sig_val_l = readl(pv->reg + ECC_U_ADDR_L_REG);
> + sig_val_h = (readl(pv->reg + ECC_U_ADDR_H_REG) &
> + ECC_ADDR_H_MASK);
> + err_u_addr = (((ulong)sig_val_h << CTL_REG_WIDTH_SHIFT) | sig_val_l);
> +
> + sig_val_l = readl(pv->reg + ECC_U_DATA_L_REG);
> + sig_val_h = readl(pv->reg + ECC_U_DATA_H_REG);
> + err_u_data = (((ulong)sig_val_h << CTL_REG_WIDTH_SHIFT) | sig_val_l);
> +
> + err_u_id = ((readl(pv->reg + ECC_U_C_ID_REG) &
> + ECC_U_ID_MASK) >> ECC_U_ID_SHIFT);
> +
> + err_u_synd = ((readl(pv->reg + ECC_U_SYND_REG) &
> + ECC_SYND_MASK) >> ECC_SYND_SHIFT);
> +
> + pv->error_count = 1;
> + pv->page_frame_number = err_u_addr >> PAGE_SHIFT;
> + pv->offset_in_page = err_u_addr & ~PAGE_MASK;
> + pv->syndrome = err_u_synd;
> + pv->top_layer = 0;
> + pv->mid_layer = 0;
> + pv->low_layer = -1;
> +
> + atomic_notifier_call_chain(&ddr_err_chain, SIFIVE_DDR_ERR_TYPE_UE, pv);
> +}
> +
> +static irqreturn_t ecc_isr(int irq, void *ptr)
> +{
> + struct sifive_ddr_priv *pv = ptr;
> + u32 intr_status;
> + u32 val;
> +
> + /* Check the intr status and confirm ECC error intr */
> + intr_status = readl(pv->reg + ECC_CTL_INT_STATUS_REG);
> +
> + dev_dbg(pv->dev, "InterruptStatus : 0x%x\n", intr_status);
> + val = intr_status & (ECC_INT_CE_UE_MASK);
> + if (!((val & ECC_CE_INTR_MASK) || (val & ECC_UE_INTR_MASK)))
> + return IRQ_NONE;
> +
> + if (val & ECC_CE_INTR_MASK) {
> + handle_ce(pv);
> +
> + /* Clear the interrupt source */
> + if (val & ECC_INT_CE_EVENT)
> + writel(ECC_INT_CE_EVENT,
> + pv->reg + ECC_CTL_INT_ACK_REG);
> + else if (val & ECC_INT_SECOND_CE_EVENT)
> + writel(ECC_INT_SECOND_CE_EVENT,
> + pv->reg + ECC_CTL_INT_ACK_REG);
> + else
> + dev_err(pv->dev, "Failed to clear IRQ\n");
> + }
> +
> + if (val & ECC_UE_INTR_MASK) {
> + handle_ue(pv);
> +
> + /* Clear the interrupt source */
> + if (val & ECC_INT_UE_EVENT)
> + writel(ECC_INT_UE_EVENT,
> + pv->reg + ECC_CTL_INT_ACK_REG);
> + else if (val & ECC_INT_SECOND_UE_EVENT)
> + writel(ECC_INT_SECOND_UE_EVENT,
> + pv->reg + ECC_CTL_INT_ACK_REG);
> + else
> + dev_err(pv->dev, "Failed to clear IRQ\n");
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int sifive_ddr_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct sifive_ddr_priv *priv;
> + struct resource *res;
> + int ret, irq;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->dev = dev;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + priv->reg = devm_ioremap_resource(dev, res);
> + if (IS_ERR(priv->reg))
> + return PTR_ERR(priv->reg);
> +
> + irq = platform_get_irq(pdev, 0);
> + ret = devm_request_irq(dev, irq, ecc_isr, 0, "sifive-fu540-ddr", priv);
> + if (ret) {
> + dev_err(dev, "request_irq failed\n");
> + return ret;
> + }
> +
> + /* Enable & set CE/UE Interrupts for DDR4 Controller */
> + writel(~(ECC_INT_CE_UE_MASK), priv->reg + ECC_CTL_INT_MASK_REG);
> +
> + platform_set_drvdata(pdev, priv);
> + dev_info(dev, "SiFive DDR probe successful\n");
I'm not sure what the rules are here, but this doesn't seem like a particularly
useful print.
> +
> + return 0;
> +}
> +
> +static int sifive_ddr_remove(struct platform_device *pdev)
> +{
> + struct sifive_ddr_priv *priv = platform_get_drvdata(pdev);
> +
> + /* Disable All ECC Interrupts for DDR4 Controller */
> + writel(ECC_INT_CE_UE_MASK, priv->reg + ECC_CTL_INT_MASK_REG);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id sifive_ddr_of_match[] = {
> + { .compatible = "sifive,fu540-c000-ddr"},
> + {},
> +};
> +
> +MODULE_DEVICE_TABLE(of, sifive_ddr_of_match);
> +
> +static struct platform_driver sifive_ddr_driver = {
> + .driver = {
> + .name = "sifive-ddr",
> + .of_match_table = sifive_ddr_of_match,
> + },
> + .probe = sifive_ddr_probe,
> + .remove = sifive_ddr_remove,
> +};
> +
> +module_platform_driver(sifive_ddr_driver);
> +
> +MODULE_AUTHOR("SiFive");
> +MODULE_DESCRIPTION("SiFive DDR Controller driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/soc/sifive/sifive_ddr.h b/include/soc/sifive/sifive_ddr.h
> new file mode 100644
> index 0000000..2ff8623
> --- /dev/null
> +++ b/include/soc/sifive/sifive_ddr.h
> @@ -0,0 +1,73 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * SiFive DDR Controller header file
> + *
> + */
> +
> +#ifndef __SOC_SIFIVE_DDR_H
> +#define __SOC_SIFIVE_DDR_H
> +
> +int register_sifive_ddr_error_notifier(struct notifier_block *nb);
> +int unregister_sifive_ddr_error_notifier(struct notifier_block *nb);
> +
> +struct sifive_ddr_priv {
> + void __iomem *reg;
> + struct device *dev;
> + u16 error_count;
> + unsigned long page_frame_number;
> + unsigned long offset_in_page;
> + unsigned long syndrome;
> + int top_layer;
> + int mid_layer;
> + int low_layer;
> +};
> +
> +#define SIFIVE_DDR_ERR_TYPE_CE (0)
> +#define SIFIVE_DDR_ERR_TYPE_UE (1)
> +#define SIFIVE_DDR_EDAC_GRAIN (1)
> +#define SIFIVE_MEM_TYPE_DDR4 0xA
> +#define SIFIVE_DDR_WIDTH_16 (2)
> +#define SIFIVE_DDR_WIDTH_32 (1)
> +#define SIFIVE_DDR_WIDTH_64 (0)
> +
> +#define DDR_CTL_MEM_TYPE_REG 0x000
> +#define DDR_CTL_MEM_WIDTH_REG 0x004
> +#define ECC_CTL_CONTROL_REG 0x174
> +#define ECC_U_ADDR_L_REG 0x180
> +#define ECC_U_ADDR_H_REG 0x184
> +#define ECC_U_DATA_L_REG 0x188
> +#define ECC_U_DATA_H_REG 0x18c
> +
> +#define ECC_C_ADDR_L_REG 0x190
> +#define ECC_C_ADDR_H_REG 0x194
> +#define ECC_C_DATA_L_REG 0x198
> +#define ECC_C_DATA_H_REG 0x19c
> +#define ECC_U_C_ID_REG 0x1A0
> +#define ECC_CTL_INT_STATUS_REG 0x210
> +#define ECC_CTL_INT_ACK_REG 0x218
> +#define ECC_CTL_INT_MASK_REG 0x220
> +#define ECC_C_SYND_REG ECC_C_ADDR_H_REG
> +#define ECC_U_SYND_REG ECC_U_ADDR_H_REG
> +
> +#define ECC_CTL_MTYPE_MASK GENMASK(11, 8)
> +#define CTL_MEM_MAX_WIDTH_MASK GENMASK(31, 24)
> +#define ECC_ADDR_H_MASK GENMASK(3, 0)
> +#define ECC_INT_CE_UE_MASK GENMASK(6, 3)
> +#define ECC_CE_INTR_MASK GENMASK(4, 3)
> +#define ECC_UE_INTR_MASK GENMASK(6, 5)
> +#define ECC_INT_CE_EVENT BIT(3)
> +#define ECC_INT_SECOND_CE_EVENT BIT(4)
> +#define ECC_INT_UE_EVENT BIT(5)
> +#define ECC_INT_SECOND_UE_EVENT BIT(6)
> +#define ECC_CTL_ECC_ENABLE BIT(16)
> +
> +#define ECC_C_ID_MASK GENMASK(28, 16)
> +#define ECC_U_ID_MASK GENMASK(12, 0)
> +#define ECC_C_ID_SHIFT (16)
> +#define ECC_U_ID_SHIFT (0)
> +#define ECC_SYND_MASK GENMASK(15, 8)
> +#define ECC_SYND_SHIFT (8)
> +
> +#define CTL_REG_WIDTH_SHIFT (32)
> +
> +#endif /* __SOC_SIFIVE_DDR_H */
Reviewed-by: Palmer Dabbelt <palmerdabbelt at google.com>
Acked-by: Palmer Dabbelt <palmerdabbelt at google.com>
More information about the linux-riscv
mailing list