@@ -225,6 +225,20 @@ config STM32_FMC2_EBI
devices (like SRAM, ethernet adapters, FPGAs, LCD displays, ...) on
SOCs containing the FMC2 External Bus Interface.
+config MEM_RAS2
+ bool "Memory RAS2 driver"
+ depends on ACPI_RAS2
+ depends on SCRUB
+ help
+ The driver bound to the platform device added by the ACPI RAS2
+ driver. Driver registers the PCC channel for communicating with
+ the ACPI compliant platform that contains RAS2 command support
+ in the hardware.
+ Registers with the scrub configure driver to provide sysfs interfaces
+ for configuring the hw patrol scrubber in the system, which exposed
+ via the ACPI RAS2 table and PCC. Provides the interface functions
+ support configuring the HW patrol scrubbers in the system.
+
source "drivers/memory/samsung/Kconfig"
source "drivers/memory/tegra/Kconfig"
source "drivers/memory/scrub/Kconfig"
@@ -7,6 +7,8 @@ obj-$(CONFIG_DDR) += jedec_ddr_data.o
ifeq ($(CONFIG_DDR),y)
obj-$(CONFIG_OF) += of_memory.o
endif
+obj-$(CONFIG_MEM_RAS2) += ras2_common.o ras2.o
+
obj-$(CONFIG_ARM_PL172_MPMC) += pl172.o
obj-$(CONFIG_ATMEL_EBI) += atmel-ebi.o
obj-$(CONFIG_BRCMSTB_DPFE) += brcmstb_dpfe.o
new file mode 100644
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ras2.c - ACPI RAS2 memory driver
+ *
+ * Copyright (c) 2023 HiSilicon Limited.
+ *
+ * - Registers the PCC channel for communicating with the
+ * ACPI compliant platform that contains RAS2 command
+ * support in the hardware.
+ * - Provides functions to configure HW patrol scrubs
+ * in the system.
+ * - Registers with the scrub configure driver for the
+ * hw patrol scrub in the system, which exposed via
+ * the ACPI RAS2 table and PCC.
+ */
+
+#define pr_fmt(fmt) "MEMORY RAS2: " fmt
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/cleanup.h>
+
+#include <acpi/ras2_acpi.h>
+#include <memory/ras2.h>
+
+/* RAS2 specific definitions. */
+#define RAS2_SCRUB "ras2_scrub"
+#define RAS2_ID_FORMAT RAS2_SCRUB "%d"
+#define RAS2_SUPPORT_HW_PARTOL_SCRUB BIT(0)
+#define RAS2_TYPE_PATROL_SCRUB 0x0000
+
+#define RAS2_GET_PATROL_PARAMETERS 0x01
+#define RAS2_START_PATROL_SCRUBBER 0x02
+#define RAS2_STOP_PATROL_SCRUBBER 0x03
+
+#define RAS2_PATROL_SCRUB_RATE_VALID BIT(0)
+#define RAS2_PATROL_SCRUB_RATE_IN_MASK GENMASK(15, 8)
+#define RAS2_PATROL_SCRUB_EN_BACKGROUND BIT(0)
+#define RAS2_PATROL_SCRUB_RATE_OUT_MASK GENMASK(7, 0)
+#define RAS2_PATROL_SCRUB_MIN_RATE_OUT_MASK GENMASK(15, 8)
+#define RAS2_PATROL_SCRUB_MAX_RATE_OUT_MASK GENMASK(23, 16)
+
+static void ras2_tx_done(struct mbox_client *cl, void *msg, int ret)
+{
+ if (ret) {
+ dev_dbg(cl->dev, "TX did not complete: CMD sent:%x, ret:%d\n",
+ *(u16 *)msg, ret);
+ } else {
+ dev_dbg(cl->dev, "TX completed. CMD sent:%x, ret:%d\n",
+ *(u16 *)msg, ret);
+ }
+}
+
+/*
+ * The below functions are exposed to OSPM, to query, configure and
+ * initiate memory patrol scrub.
+ */
+static int ras2_is_patrol_scrub_support(struct ras2_context *ras2_ctx)
+{
+ int ret;
+ struct acpi_ras2_shared_memory __iomem *generic_comm_base;
+
+ if (!ras2_ctx || !ras2_ctx->pcc_comm_addr)
+ return -EFAULT;
+
+ generic_comm_base = ras2_ctx->pcc_comm_addr;
+ guard(spinlock_irqsave)(&ras2_ctx->spinlock);
+ generic_comm_base->set_capabilities[0] = 0;
+
+ /* send command for reading RAS2 capabilities */
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "%s: ras2_send_pcc_cmd failed\n", __func__);
+ return ret;
+ }
+
+ return generic_comm_base->features[0] & RAS2_SUPPORT_HW_PARTOL_SCRUB;
+}
+
+static int ras2_get_patrol_scrub_params(struct ras2_context *ras2_ctx,
+ struct ras2_scrub_params *params)
+{
+ int ret = 0;
+ u8 min_supp_scrub_rate, max_supp_scrub_rate;
+ struct acpi_ras2_shared_memory __iomem *generic_comm_base;
+ struct acpi_ras2_patrol_scrub_parameter __iomem *patrol_scrub_params;
+
+ if (!ras2_ctx || !ras2_ctx->pcc_comm_addr)
+ return -EFAULT;
+
+ generic_comm_base = ras2_ctx->pcc_comm_addr;
+ patrol_scrub_params = ras2_ctx->pcc_comm_addr + sizeof(*generic_comm_base);
+
+ guard(spinlock_irqsave)(&ras2_ctx->spinlock);
+ generic_comm_base->set_capabilities[0] = RAS2_SUPPORT_HW_PARTOL_SCRUB;
+ /* send command for reading RAS2 capabilities */
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "%s: ras2_send_pcc_cmd failed\n", __func__);
+ return ret;
+ }
+
+ if (!(generic_comm_base->features[0] & RAS2_SUPPORT_HW_PARTOL_SCRUB) ||
+ !(generic_comm_base->num_parameter_blocks)) {
+ dev_err(ras2_ctx->dev,
+ "%s: Platform does not support HW Patrol Scrubber\n", __func__);
+ return -EOPNOTSUPP;
+ }
+
+ if (!patrol_scrub_params->requested_address_range[1]) {
+ dev_err(ras2_ctx->dev,
+ "%s: Invalid requested address range, \
+ requested_address_range[0]=0x%llx \
+ requested_address_range[1]=0x%llx\n",
+ __func__,
+ patrol_scrub_params->requested_address_range[0],
+ patrol_scrub_params->requested_address_range[1]);
+ return -EOPNOTSUPP;
+ }
+
+ generic_comm_base->set_capabilities[0] = RAS2_SUPPORT_HW_PARTOL_SCRUB;
+ patrol_scrub_params->header.type = RAS2_TYPE_PATROL_SCRUB;
+ patrol_scrub_params->patrol_scrub_command = RAS2_GET_PATROL_PARAMETERS;
+
+ /* send command for reading the HW patrol scrub parameters */
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "%s: failed to read HW patrol scrub parameters\n",
+ __func__);
+ return ret;
+ }
+
+ /* copy output scrub parameters */
+ params->addr_base = patrol_scrub_params->actual_address_range[0];
+ params->addr_size = patrol_scrub_params->actual_address_range[1];
+ params->flags = patrol_scrub_params->flags;
+ params->rate = FIELD_GET(RAS2_PATROL_SCRUB_RATE_OUT_MASK,
+ patrol_scrub_params->scrub_params_out);
+ min_supp_scrub_rate = FIELD_GET(RAS2_PATROL_SCRUB_MIN_RATE_OUT_MASK,
+ patrol_scrub_params->scrub_params_out);
+ max_supp_scrub_rate = FIELD_GET(RAS2_PATROL_SCRUB_MAX_RATE_OUT_MASK,
+ patrol_scrub_params->scrub_params_out);
+ snprintf(params->rate_avail, RAS2_MAX_RATE_RANGE_LENGTH,
+ "%d-%d", min_supp_scrub_rate, max_supp_scrub_rate);
+
+ return 0;
+}
+
+static int ras2_enable_patrol_scrub(struct ras2_context *ras2_ctx, bool enable)
+{
+ int ret = 0;
+ struct ras2_scrub_params params;
+ struct acpi_ras2_shared_memory __iomem *generic_comm_base;
+ u8 scrub_rate_to_set, min_supp_scrub_rate, max_supp_scrub_rate;
+ struct acpi_ras2_patrol_scrub_parameter __iomem *patrol_scrub_params;
+
+ if (!ras2_ctx || !ras2_ctx->pcc_comm_addr)
+ return -EFAULT;
+
+ generic_comm_base = ras2_ctx->pcc_comm_addr;
+ patrol_scrub_params = ras2_ctx->pcc_comm_addr + sizeof(*generic_comm_base);
+
+ if (enable) {
+ ret = ras2_get_patrol_scrub_params(ras2_ctx, ¶ms);
+ if (ret)
+ return ret;
+ }
+
+ guard(spinlock_irqsave)(&ras2_ctx->spinlock);
+ generic_comm_base->set_capabilities[0] = RAS2_SUPPORT_HW_PARTOL_SCRUB;
+ patrol_scrub_params->header.type = RAS2_TYPE_PATROL_SCRUB;
+
+ if (enable) {
+ patrol_scrub_params->patrol_scrub_command = RAS2_START_PATROL_SCRUBBER;
+ patrol_scrub_params->requested_address_range[0] = params.addr_base;
+ patrol_scrub_params->requested_address_range[1] = params.addr_size;
+
+ scrub_rate_to_set = FIELD_GET(RAS2_PATROL_SCRUB_RATE_IN_MASK,
+ patrol_scrub_params->scrub_params_in);
+ min_supp_scrub_rate = FIELD_GET(RAS2_PATROL_SCRUB_MIN_RATE_OUT_MASK,
+ patrol_scrub_params->scrub_params_out);
+ max_supp_scrub_rate = FIELD_GET(RAS2_PATROL_SCRUB_MAX_RATE_OUT_MASK,
+ patrol_scrub_params->scrub_params_out);
+ if (scrub_rate_to_set < min_supp_scrub_rate ||
+ scrub_rate_to_set > max_supp_scrub_rate) {
+ dev_warn(ras2_ctx->dev,
+ "patrol scrub rate to set is out of the supported range\n");
+ dev_warn(ras2_ctx->dev,
+ "min_supp_scrub_rate=%d max_supp_scrub_rate=%d\n",
+ min_supp_scrub_rate, max_supp_scrub_rate);
+ return -EINVAL;
+ }
+ } else {
+ patrol_scrub_params->patrol_scrub_command = RAS2_STOP_PATROL_SCRUBBER;
+ }
+
+ /* send command for enable/disable HW patrol scrub */
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ pr_err("%s: failed to enable/disable the HW patrol scrub\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ras2_enable_background_scrub(struct ras2_context *ras2_ctx, bool enable)
+{
+ int ret;
+ struct acpi_ras2_shared_memory __iomem *generic_comm_base;
+ struct acpi_ras2_patrol_scrub_parameter __iomem *patrol_scrub_params;
+
+ if (!ras2_ctx || !ras2_ctx->pcc_comm_addr)
+ return -EFAULT;
+
+ generic_comm_base = ras2_ctx->pcc_comm_addr;
+ patrol_scrub_params = ras2_ctx->pcc_comm_addr + sizeof(*generic_comm_base);
+
+ guard(spinlock_irqsave)(&ras2_ctx->spinlock);
+ generic_comm_base->set_capabilities[0] = RAS2_SUPPORT_HW_PARTOL_SCRUB;
+ patrol_scrub_params->header.type = RAS2_TYPE_PATROL_SCRUB;
+ patrol_scrub_params->patrol_scrub_command = RAS2_START_PATROL_SCRUBBER;
+
+ patrol_scrub_params->scrub_params_in &= ~RAS2_PATROL_SCRUB_EN_BACKGROUND;
+ patrol_scrub_params->scrub_params_in |= FIELD_PREP(RAS2_PATROL_SCRUB_EN_BACKGROUND,
+ enable);
+
+ /* send command for enable/disable HW patrol scrub */
+ ret = ras2_send_pcc_cmd(ras2_ctx, RAS2_PCC_CMD_EXEC);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "%s: failed to enable/disable background patrol scrubbing\n",
+ __func__);
+ return ret;
+ }
+
+ return 0;
+}
+static int ras2_set_patrol_scrub_params(struct ras2_context *ras2_ctx,
+ struct ras2_scrub_params *params, u8 param_type)
+{
+ struct acpi_ras2_shared_memory __iomem *generic_comm_base;
+ struct acpi_ras2_patrol_scrub_parameter __iomem *patrol_scrub_params;
+
+ if (!ras2_ctx || !ras2_ctx->pcc_comm_addr)
+ return -EFAULT;
+
+ generic_comm_base = ras2_ctx->pcc_comm_addr;
+ patrol_scrub_params = ras2_ctx->pcc_comm_addr + sizeof(*generic_comm_base);
+
+ guard(spinlock_irqsave)(&ras2_ctx->spinlock);
+ patrol_scrub_params->header.type = RAS2_TYPE_PATROL_SCRUB;
+ if (param_type == RAS2_MEM_SCRUB_PARAM_ADDR_BASE && params->addr_base) {
+ patrol_scrub_params->requested_address_range[0] = params->addr_base;
+ } else if (param_type == RAS2_MEM_SCRUB_PARAM_ADDR_SIZE && params->addr_size) {
+ patrol_scrub_params->requested_address_range[1] = params->addr_size;
+ } else if (param_type == RAS2_MEM_SCRUB_PARAM_RATE) {
+ patrol_scrub_params->scrub_params_in &= ~RAS2_PATROL_SCRUB_RATE_IN_MASK;
+ patrol_scrub_params->scrub_params_in |= FIELD_PREP(RAS2_PATROL_SCRUB_RATE_IN_MASK,
+ params->rate);
+ } else {
+ dev_err(ras2_ctx->dev, "Invalid patrol scrub parameter to set\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct ras2_hw_scrub_ops ras2_hw_ops = {
+ .enable_scrub = ras2_enable_patrol_scrub,
+ .enable_background_scrub = ras2_enable_background_scrub,
+ .get_scrub_params = ras2_get_patrol_scrub_params,
+ .set_scrub_params = ras2_set_patrol_scrub_params,
+};
+
+static const struct scrub_ops ras2_scrub_ops = {
+ .is_visible = ras2_hw_scrub_is_visible,
+ .read = ras2_hw_scrub_read,
+ .write = ras2_hw_scrub_write,
+ .read_string = ras2_hw_scrub_read_strings,
+};
+
+static DEFINE_IDA(ras2_ida);
+
+static void devm_ras2_release(void *ctx)
+{
+ struct ras2_context *ras2_ctx = ctx;
+
+ ida_free(&ras2_ida, ras2_ctx->id);
+ ras2_unregister_pcc_channel(ras2_ctx);
+}
+
+static int ras2_probe(struct platform_device *pdev)
+{
+ int ret, id;
+ struct mbox_client *cl;
+ struct device *hw_scrub_dev;
+ struct ras2_context *ras2_ctx;
+ char scrub_name[RAS2_MAX_NAME_LENGTH];
+
+ ras2_ctx = devm_kzalloc(&pdev->dev, sizeof(*ras2_ctx), GFP_KERNEL);
+ if (!ras2_ctx)
+ return -ENOMEM;
+
+ ras2_ctx->dev = &pdev->dev;
+ ras2_ctx->ops = &ras2_hw_ops;
+ spin_lock_init(&ras2_ctx->spinlock);
+ platform_set_drvdata(pdev, ras2_ctx);
+
+ cl = &ras2_ctx->mbox_client;
+ /* Request mailbox channel */
+ cl->dev = &pdev->dev;
+ cl->tx_done = ras2_tx_done;
+ cl->knows_txdone = true;
+ ras2_ctx->pcc_subspace_idx = *((int *)pdev->dev.platform_data);
+ dev_dbg(&pdev->dev, "pcc-subspace-id=%d\n", ras2_ctx->pcc_subspace_idx);
+ ret = ras2_register_pcc_channel(ras2_ctx);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&pdev->dev, devm_ras2_release, ras2_ctx);
+ if (ret < 0)
+ return ret;
+
+ if (ras2_is_patrol_scrub_support(ras2_ctx)) {
+ id = ida_alloc(&ras2_ida, GFP_KERNEL);
+ if (id < 0)
+ return id;
+ ras2_ctx->id = id;
+ snprintf(scrub_name, sizeof(scrub_name), "%s%d", RAS2_SCRUB, id);
+ dev_set_name(&pdev->dev, RAS2_ID_FORMAT, id);
+ hw_scrub_dev = devm_scrub_device_register(&pdev->dev, scrub_name,
+ ras2_ctx, &ras2_scrub_ops,
+ 0, NULL);
+ if (PTR_ERR_OR_ZERO(hw_scrub_dev))
+ return PTR_ERR_OR_ZERO(hw_scrub_dev);
+ }
+ ras2_ctx->scrub_dev = hw_scrub_dev;
+
+ return 0;
+}
+
+static const struct platform_device_id ras2_id_table[] = {
+ { .name = "ras2", },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, ras2_id_table);
+
+static struct platform_driver ras2_driver = {
+ .probe = ras2_probe,
+ .driver = {
+ .name = "ras2",
+ .suppress_bind_attrs = true,
+ },
+ .id_table = ras2_id_table,
+};
+module_driver(ras2_driver, platform_driver_register, platform_driver_unregister);
+
+MODULE_DESCRIPTION("ras2 memory driver");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Common functions for memory RAS2 driver
+ *
+ * Copyright (c) 2024 HiSilicon Limited.
+ *
+ * This driver implements call back functions for the scrub
+ * configure driver to configure the parameters of the hw patrol
+ * scrubbers in the system, which exposed via the ACPI AS2
+ * table and PCC.
+ */
+
+#include <linux/acpi.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/mailbox_controller.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <acpi/ras2_acpi.h>
+#include <memory/ras2.h>
+
+static int enable_write(struct ras2_context *ras2_ctx, long val)
+{
+ int ret;
+ bool enable = val;
+
+ ret = ras2_ctx->ops->enable_scrub(ras2_ctx, enable);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "enable patrol scrub fail, enable=%d ret=%d\n",
+ enable, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int enable_background_scrub_write(struct ras2_context *ras2_ctx, long val)
+{
+ int ret;
+ bool enable = val;
+
+ ret = ras2_ctx->ops->enable_background_scrub(ras2_ctx, enable);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "enable background patrol scrub fail, enable=%d ret=%d\n",
+ enable, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int addr_base_read(struct ras2_context *ras2_ctx, u64 *val)
+{
+ int ret;
+ struct ras2_scrub_params params;
+
+ ret = ras2_ctx->ops->get_scrub_params(ras2_ctx, ¶ms);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "get patrol scrub params fail ret=%d\n", ret);
+ return ret;
+ }
+ *val = params.addr_base;
+
+ return 0;
+}
+
+static int addr_base_write(struct ras2_context *ras2_ctx, u64 val)
+{
+ int ret;
+ struct ras2_scrub_params params;
+
+ params.addr_base = val;
+ ret = ras2_ctx->ops->set_scrub_params(ras2_ctx, ¶ms,
+ RAS2_MEM_SCRUB_PARAM_ADDR_BASE);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "set patrol scrub params for addr_base fail ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int addr_size_read(struct ras2_context *ras2_ctx, u64 *val)
+{
+ int ret;
+ struct ras2_scrub_params params;
+
+ ret = ras2_ctx->ops->get_scrub_params(ras2_ctx, ¶ms);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "get patrol scrub params fail ret=%d\n", ret);
+ return ret;
+ }
+ *val = params.addr_size;
+
+ return 0;
+}
+
+static int addr_size_write(struct ras2_context *ras2_ctx, u64 val)
+{
+ int ret;
+ struct ras2_scrub_params params;
+
+ params.addr_size = val;
+ ret = ras2_ctx->ops->set_scrub_params(ras2_ctx, ¶ms,
+ RAS2_MEM_SCRUB_PARAM_ADDR_SIZE);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "set patrol scrub params for addr_size fail ret=%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rate_read(struct ras2_context *ras2_ctx, u64 *val)
+{
+ int ret;
+ struct ras2_scrub_params params;
+
+ ret = ras2_ctx->ops->get_scrub_params(ras2_ctx, ¶ms);
+ if (ret) {
+ dev_err(ras2_ctx->dev, "get patrol scrub params fail ret=%d\n",
+ ret);
+ return ret;
+ }
+ *val = params.rate;
+
+ return 0;
+}
+
+static int rate_write(struct ras2_context *ras2_ctx, long val)
+{
+ int ret;
+ struct ras2_scrub_params params;
+
+ params.rate = val;
+ ret = ras2_ctx->ops->set_scrub_params(ras2_ctx, ¶ms,
+ RAS2_MEM_SCRUB_PARAM_RATE);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "set patrol scrub params for rate fail ret=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rate_available_read(struct ras2_context *ras2_ctx, char *buf)
+{
+ int ret;
+ struct ras2_scrub_params params;
+
+ ret = ras2_ctx->ops->get_scrub_params(ras2_ctx, ¶ms);
+ if (ret) {
+ dev_err(ras2_ctx->dev,
+ "get patrol scrub params fail ret=%d\n", ret);
+ return ret;
+ }
+
+ sprintf(buf, "%s\n", params.rate_avail);
+
+ return 0;
+}
+
+/**
+ * ras2_hw_scrub_is_visible() - Callback to return attribute visibility
+ * @drv_data: Pointer to driver-private data structure passed
+ * as argument to devm_scrub_device_register().
+ * @attr_id: Scrub attribute
+ * @mode: attribute's mode
+ * @region_id: ID of the memory region
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+umode_t ras2_hw_scrub_is_visible(struct device *dev, u32 attr_id,
+ umode_t mode, int region_id)
+{
+ switch (attr_id) {
+ case scrub_rate_available:
+ case scrub_enable:
+ case scrub_enable_background_scrub:
+ case scrub_addr_base:
+ case scrub_addr_size:
+ case scrub_rate:
+ return mode;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * ras2_hw_scrub_read() - Read callback for data attributes
+ * @device: Pointer to scrub device
+ * @attr_id: Scrub attribute
+ * @region_id: ID of the memory region
+ * @val: Pointer to the returned data
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+int ras2_hw_scrub_read(struct device *device, u32 attr_id,
+ int region_id, u64 *val)
+{
+ struct ras2_context *ras2_ctx;
+
+ ras2_ctx = dev_get_drvdata(device);
+
+ switch (attr_id) {
+ case scrub_addr_base:
+ return addr_base_read(ras2_ctx, val);
+ case scrub_addr_size:
+ return addr_size_read(ras2_ctx, val);
+ case scrub_rate:
+ return rate_read(ras2_ctx, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/**
+ * ras2_hw_scrub_write() - Write callback for data attributes
+ * @device: Pointer to scrub device
+ * @attr_id: Scrub attribute
+ * @region_id: ID of the memory region
+ * @val: Value to write
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+int ras2_hw_scrub_write(struct device *device, u32 attr_id,
+ int region_id, u64 val)
+{
+ struct ras2_context *ras2_ctx;
+
+ ras2_ctx = dev_get_drvdata(device);
+
+ switch (attr_id) {
+ case scrub_addr_base:
+ return addr_base_write(ras2_ctx, val);
+ case scrub_addr_size:
+ return addr_size_write(ras2_ctx, val);
+ case scrub_enable:
+ return enable_write(ras2_ctx, val);
+ case scrub_enable_background_scrub:
+ return enable_background_scrub_write(ras2_ctx, val);
+ case scrub_rate:
+ return rate_write(ras2_ctx, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/**
+ * ras2_hw_scrub_read_strings() - Read callback for string attributes
+ * @device: Pointer to scrub device
+ * @attr_id: Scrub attribute
+ * @region_id: ID of the memory region
+ * @buf: Pointer to the buffer for copying returned string
+ *
+ * Returns: 0 on success, an error otherwise
+ */
+int ras2_hw_scrub_read_strings(struct device *dev, u32 attr_id,
+ int region_id, char *buf)
+{
+ struct ras2_context *ras2_ctx;
+
+ ras2_ctx = dev_get_drvdata(dev);
+
+ switch (attr_id) {
+ case scrub_rate_available:
+ return rate_available_read(ras2_ctx, buf);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
new file mode 100755
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */
+/*
+ * Memory RAS2 driver header file
+ *
+ * Copyright (c) 2024 HiSilicon Limited
+ */
+
+#ifndef _RAS2_H
+#define _RAS2_H
+
+#include <memory/memory-scrub.h>
+
+#define RAS2_MAX_NAME_LENGTH 64
+#define RAS2_MAX_RATE_RANGE_LENGTH 64
+
+/*
+ * Data structures RAS2
+ */
+
+/**
+ * struct ras2_scrub_params- RAS2 scrub parameter data structure.
+ * @addr_base: [IN] Base address of the address range to be patrol scrubbed.
+ * [OUT] Base address of the actual address range.
+ * @addr_size: [IN] Size of the address range to be patrol scrubbed.
+ * [OUT] Size of the actual address range.
+ * @flags: [OUT] The platform returns this value in response to
+ * GET_PATROL_PARAMETERS.
+ * For RAS2:
+ * Bit [0]: Will be set if memory scrubber is already
+ * running for address range specified in “Actual Address Range”.
+ * @rate: [IN] Requested patrol scrub rate.
+ * [OUT] Current patrol scrub rate.
+ * @rate_avail:[OUT] Supported patrol rates.
+ */
+struct ras2_scrub_params {
+ u64 addr_base;
+ u64 addr_size;
+ u16 flags;
+ u32 rate;
+ char rate_avail[RAS2_MAX_RATE_RANGE_LENGTH];
+};
+
+enum {
+ RAS2_MEM_SCRUB_PARAM_ADDR_BASE = 0,
+ RAS2_MEM_SCRUB_PARAM_ADDR_SIZE,
+ RAS2_MEM_SCRUB_PARAM_RATE,
+};
+
+/**
+ * struct ras2_hw_scrub_ops - ras2 hw scrub device operations
+ * @enable_scrub: Function to enable/disable RAS2 scrubber.
+ * Parameters are:
+ * @ras2_ctx: Pointer to RAS2 context structure.
+ * @enable: enable/disable RAS2 patrol scrubber.
+ * The function returns 0 on success or a negative error number.
+ * @enable_background_scrub: Function to enable/disable background scrubbing.
+ * Parameters are:
+ * @ras2_ctx: Pointer to RAS2 context structure.
+ * @enable: enable/disable background patrol scrubbing.
+ * The function returns 0 on success or a negative error number.
+ * @get_scrub_params: Read scrubber parameters. Mandatory
+ * Parameters are:
+ * @ras2_ctx: Pointer to RAS2 context structure.
+ * @params: Pointer to scrub params data structure.
+ * The function returns 0 on success or a negative error number.
+ * @set_scrub_params: Set scrubber parameters. Mandatory.
+ * Parameters are:
+ * @ras2_ctx: Pointer to RAS2 context structure.
+ * @params: Pointer to scrub params data structure.
+ * @param_type: Scrub parameter type to set.
+ * The function returns 0 on success or a negative error number.
+ */
+struct ras2_hw_scrub_ops {
+ int (*enable_scrub)(struct ras2_context *ras2_ctx, bool enable);
+ int (*enable_background_scrub)(struct ras2_context *ras2_ctx, bool enable);
+ int (*get_scrub_params)(struct ras2_context *ras2_ctx,
+ struct ras2_scrub_params *params);
+ int (*set_scrub_params)(struct ras2_context *ras2_ctx,
+ struct ras2_scrub_params *params, u8 param_type);
+};
+
+umode_t ras2_hw_scrub_is_visible(struct device *dev, u32 attr_id,
+ umode_t mode, int region_id);
+int ras2_hw_scrub_read(struct device *dev, u32 attr_id, int region_id, u64 *val);
+int ras2_hw_scrub_write(struct device *dev, u32 attr_id, int region_id, u64 val);
+int ras2_hw_scrub_read_strings(struct device *dev, u32 attr_id,
+ int region_id, char *buf);
+#endif /* _RAS2_H */