// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include static const struct regmap_config memmax_regmap_cfg = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .val_format_endian = REGMAP_ENDIAN_BIG, .name = "memmax", }; enum memmax_fields { F_QOS_MODE, F_PAGEMODE, F_STARVATION_COUNTER, F_REQUEST_GROUP_SIZE, F_BANDWIDTH_RATE, F_WORDS_PER_PERIOD, F_WORDS_PER_PERIOD_MAX, F_MAX_FIELDS, }; #define MEMMAX_REG_NAME_MAX 64 #define MEMMAX_REG_PROP_PRE "sonics,memmax-thread-" static const char memmax_field_names[F_MAX_FIELDS][MEMMAX_REG_NAME_MAX] = { [F_QOS_MODE] = "qos-mode", [F_PAGEMODE] = "pagemode", [F_STARVATION_COUNTER] = "starvation-counter", [F_REQUEST_GROUP_SIZE] = "request-group-size", [F_BANDWIDTH_RATE] = "bandwidth-rate", [F_WORDS_PER_PERIOD] = "words-per-period", [F_WORDS_PER_PERIOD_MAX] = "words-per-period-max", }; #define MEMMAX_THREADSCH_LOW_BASE 0x20 #define MEMMAX_THREADSCH_HIG_BASE 0x24 #define MEMMAX_THREADSCHEXT_LOW_BASE 0x28 #define MEMMAX_THREADSCH_STRIDE 0x10 #define MEMMAX_THREADSCH_LOW(i) (MEMMAX_THREADSCH_LOW_BASE + (i) * MEMMAX_THREADSCH_STRIDE) #define MEMMAX_THREADSCH_HIG(i) (MEMMAX_THREADSCH_HIG_BASE + (i) * MEMMAX_THREADSCH_STRIDE) #define MEMMAX_THREADSCHEXT_LOW(i) (MEMMAX_THREADSCHEXT_LOW_BASE + (i) * MEMMAX_THREADSCH_STRIDE) #define MEMMAX_THREAD_FIELD_LAYOUT(i) \ { \ [F_QOS_MODE] = REG_FIELD(MEMMAX_THREADSCH_LOW(i), 0, 1), \ [F_PAGEMODE] = REG_FIELD(MEMMAX_THREADSCH_LOW(i), 7, 7), \ [F_STARVATION_COUNTER] = REG_FIELD(MEMMAX_THREADSCH_LOW(i), 8, 15), \ [F_REQUEST_GROUP_SIZE] = REG_FIELD(MEMMAX_THREADSCH_LOW(i), 24, 31), \ [F_BANDWIDTH_RATE] = REG_FIELD(MEMMAX_THREADSCH_HIG(i), 0, 15), \ [F_WORDS_PER_PERIOD] = REG_FIELD(MEMMAX_THREADSCH_HIG(i), 16, 31), \ [F_WORDS_PER_PERIOD_MAX] = REG_FIELD(MEMMAX_THREADSCHEXT_LOW(i), 0, 15), \ } static struct dentry *debugfs_root; #define DIR_PREFIX "memmax@" #define DIR_SUFFIX_LEN (sizeof(resource_size_t) * 2) struct memmax_prop_data { struct regmap_field *field; struct dentry *file; }; struct memmax_thread_data { struct memmax_dev_data *dev_data; u32 dt_idx; const char *name; struct regmap_field *fields[F_MAX_FIELDS]; struct dentry *debugfs_dir; struct memmax_prop_data debugfs_props[F_MAX_FIELDS]; }; struct memmax_dev_data { struct platform_device *pdev; struct regmap *regmap; size_t num_threads; struct memmax_thread_data *thread_data; struct dentry *debugfs_dir; }; static int memmax_init_thread_fields(struct memmax_thread_data *thread_data, u32 reg_idx) { struct memmax_dev_data *dev_data = thread_data->dev_data; struct device *dev = &dev_data->pdev->dev; struct reg_field field_layouts[F_MAX_FIELDS] = MEMMAX_THREAD_FIELD_LAYOUT(reg_idx); size_t i; for (i = 0; i < F_MAX_FIELDS; ++i) { thread_data->fields[i] = devm_regmap_field_alloc(dev, dev_data->regmap, field_layouts[i]); if (IS_ERR(thread_data->fields[i])) return PTR_ERR(thread_data->fields[i]); } return 0; } static int memmax_init_thread_dt(struct memmax_thread_data *thread_data) { struct memmax_dev_data *dev_data = thread_data->dev_data; struct device *dev = &dev_data->pdev->dev; struct device_node *dev_node = dev->of_node; u32 reg_idx; int ret; size_t i; ret = of_property_read_u32_index(dev_node, "sonics,memmax-threads", thread_data->dt_idx, ®_idx); if (ret < 0) return ret; ret = of_property_read_string_index(dev_node, "sonics,memmax-thread-names", thread_data->dt_idx, &thread_data->name); if (ret < 0) return ret; ret = memmax_init_thread_fields(thread_data, reg_idx); if (ret < 0) return ret; for (i = 0; i < F_MAX_FIELDS; ++i) { const char *field_name = memmax_field_names[i]; struct regmap_field *field = thread_data->fields[i]; char prop_name[sizeof(MEMMAX_REG_PROP_PRE) - 1 + MEMMAX_REG_NAME_MAX]; u32 val; ret = snprintf(prop_name, sizeof(prop_name), MEMMAX_REG_PROP_PRE "%s", field_name); if (ret < 0 || ret >= sizeof(prop_name)) return -ENODEV; ret = of_property_read_u32_index(dev_node, prop_name, thread_data->dt_idx, &val); if (ret < 0 || val == SONICS_MEMMAX_RESET_VALUE) continue; dev_info(dev, "%s: setting %s to 0x%x\n", thread_data->name, field_name, val); ret = regmap_field_write(field, val); if (ret < 0) return ret; } return 0; } #if IS_ENABLED(CONFIG_DEBUG_FS) static int memmax_prop_set(void *data, u64 val) { const struct memmax_prop_data *prop_data = data; if (val > U32_MAX) return -ERANGE; return regmap_field_write(prop_data->field, val); } static int memmax_prop_get(void *data, u64 *val) { const struct memmax_prop_data *prop_data = data; int tmp, ret; ret = regmap_field_read(prop_data->field, &tmp); if (ret < 0) return ret; if (tmp < 0) return -ERANGE; *val = tmp; return 0; } DEFINE_DEBUGFS_ATTRIBUTE(memmax_prop_fops, memmax_prop_get, memmax_prop_set, "%llu\n"); static bool memmax_init_thread_debugfs(struct memmax_thread_data *thread_data) { const struct memmax_dev_data *dev_data = thread_data->dev_data; size_t i; if (!debugfs_root) return 1; thread_data->debugfs_dir = debugfs_create_dir(thread_data->name, dev_data->debugfs_dir); if (!thread_data->debugfs_dir) return 0; for (i = 0; i < F_MAX_FIELDS; ++i) { struct memmax_prop_data *prop_data = &thread_data->debugfs_props[i]; const char *field_name = memmax_field_names[i]; prop_data->field = thread_data->fields[i]; prop_data->file = debugfs_create_file(field_name, 0600, thread_data->debugfs_dir, prop_data, &memmax_prop_fops); if (IS_ERR_OR_NULL(prop_data->file)) goto fail_loop; } return 1; fail_loop: debugfs_remove_recursive(thread_data->debugfs_dir); return 0; } #else static bool memmax_init_thread_debugfs(struct memmax_thread_data *thread_data, struct device_node *node) { return 1; } #endif static int memmax_init_thread(struct memmax_thread_data *thread_data) { const struct memmax_dev_data *dev_data = thread_data->dev_data; struct device *dev = &dev_data->pdev->dev; int ret; ret = memmax_init_thread_dt(thread_data); if (ret < 0) return ret; if (!memmax_init_thread_debugfs(thread_data)) { dev_err(dev, "%s: failed to create the debugfs directory\n", thread_data->name); /* no error return, as debugfs is optional */ } return 0; } static void memmax_destroy_thread(struct memmax_dev_data *data, struct memmax_thread_data *thread) { debugfs_remove_recursive(thread->debugfs_dir); } static void memmax_destroy_threads(struct memmax_dev_data *data) { size_t i; for (i = 0; i < data->num_threads; ++i) memmax_destroy_thread(data, &data->thread_data[i]); } static int memmax_probe(struct platform_device *pdev) { struct memmax_dev_data *dev_data; struct resource *reg_res; struct device *dev = &pdev->dev; void __iomem *mmio; /* Suffix with at most one 64-bit address in hex. */ char dirname[sizeof(DIR_PREFIX) + DIR_SUFFIX_LEN]; size_t i; int ret; reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (unlikely(!reg_res)) { dev_err(dev, "no memory resource\n"); return -ENODEV; } dev_data = devm_kzalloc(dev, sizeof(*dev_data), GFP_KERNEL); if (!dev_data) return -ENOMEM; dev_data->pdev = pdev; mmio = devm_ioremap_resource(dev, reg_res); if (IS_ERR(mmio)) return PTR_ERR(mmio); dev_data->regmap = devm_regmap_init_mmio(dev, mmio, &memmax_regmap_cfg); if (IS_ERR(dev_data->regmap)) return PTR_ERR(dev_data->regmap); ret = of_property_count_u32_elems(dev->of_node, "sonics,memmax-threads"); if (ret < 0) { dev_err(dev, "missing the sonics,memmax-threads device tree property"); return -ENODEV; } dev_data->num_threads = ret; dev_data->thread_data = devm_kcalloc(dev, dev_data->num_threads, sizeof(*dev_data->thread_data), GFP_KERNEL); if (!dev_data->thread_data) return -ENOMEM; ret = snprintf(dirname, sizeof(dirname), DIR_PREFIX "%0*x", (int)DIR_SUFFIX_LEN, reg_res->start); if (ret < 0 || ret >= sizeof(dirname)) { dev_err(dev, "failed to build the debugfs directory name"); return -ENODEV; } if (debugfs_root) { dev_data->debugfs_dir = debugfs_create_dir(dirname, debugfs_root); if (IS_ERR_OR_NULL(dev_data->debugfs_dir)) { dev_err(dev, "failed to create the debugfs directory '%s'", dirname); /* no error return, as debugfs is optional */ } } for (i = 0; i < dev_data->num_threads; ++i) { struct memmax_thread_data *thread_data = &dev_data->thread_data[i]; thread_data->dev_data = dev_data; thread_data->dt_idx = i; ret = memmax_init_thread(thread_data); if (ret < 0) { dev_data->num_threads = i; memmax_destroy_threads(dev_data); return ret; } } platform_set_drvdata(pdev, dev_data); return 0; } static int memmax_remove(struct platform_device *pdev) { struct memmax_dev_data *dev_data = platform_get_drvdata(pdev); memmax_destroy_threads(dev_data); debugfs_remove_recursive(dev_data->debugfs_dir); return 0; } static const struct of_device_id memmax_of_ids[] = { { .compatible = "sonics,memmax" }, { }, }; MODULE_DEVICE_TABLE(of, memmax_of_ids); static struct platform_driver memmax_driver = { .driver = { .name = "sonics-memmax", .owner = THIS_MODULE, .of_match_table = memmax_of_ids, }, .probe = memmax_probe, .remove = memmax_remove, }; static int __init memmax_init(void) { int ret; debugfs_root = debugfs_create_dir("sonics-memmax", NULL); if (IS_ERR_OR_NULL(debugfs_root)) { if (PTR_ERR(debugfs_root) != -ENODEV) pr_err("%s: failed to create the debugfs directory 'sonics-memmax'\n", __func__); debugfs_root = NULL; /* no error return, as debugfs is optional */ } ret = platform_driver_register(&memmax_driver); if (ret < 0) { debugfs_remove(debugfs_root); debugfs_root = NULL; return ret; } return 0; } static void memmax_exit(void) { platform_driver_unregister(&memmax_driver); debugfs_remove_recursive(debugfs_root); } module_init(memmax_init); module_exit(memmax_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Johannes Nixdorf "); MODULE_DESCRIPTION("Driver for the Sonics MemMax DDR memory scheduler");