[RFC PATCH 02/11] soc: tegra: Add config setting framework

Krishna Yarlagadda
2024-05-06T22:52:41Z

Config settings are defined as a property per field and have different
modes per device. Each mode contains multiple properties and a device
can have multiple modes.
Config framework parses device tree and provides a list of register
settings with mask per mode to be applied by the controller.
Add APIs to parse list of register config settings and to get config
from the list by name to be applied.

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Signed-off-by: Krishna Yarlagadda <kyarlagadda@nvidia.com>
---
MAINTAINERS | 1 +
drivers/soc/tegra/Makefile | 1 +
drivers/soc/tegra/tegra-cfg.c | 147 ++++++++++++++++++++++++++++++++++
include/soc/tegra/tegra-cfg.h | 87 ++++++++++++++++++++
4 files changed, 236 insertions(+)
create mode 100644 drivers/soc/tegra/tegra-cfg.c
create mode 100644 include/soc/tegra/tegra-cfg.h

diff --git a/MAINTAINERS b/MAINTAINERS
index c788ff0506c3..9eeb749ef234 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21997,6 +21997,7 @@ R: Laxman Dewangan <ldewangan@nvidia.com>
R: Krishna Yarlagadda <kyarlagadda@nvidia.com>
S: Supported
F: Documentation/misc-devices/tegra-cfg.rst
+F: drivers/soc/tegra/tegra-cfg.c

TEGRA PWM DRIVER
M: Thierry Reding <thierry.reding@gmail.com>
diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile
index 01059619e764..8d0c8dc62c8c 100644
--- a/drivers/soc/tegra/Makefile
+++ b/drivers/soc/tegra/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o
obj-$(CONFIG_SOC_TEGRA20_VOLTAGE_COUPLER) += regulators-tegra20.o
obj-$(CONFIG_SOC_TEGRA30_VOLTAGE_COUPLER) += regulators-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_186_SOC) += ari-tegra186.o
+obj-y += tegra-cfg.o
diff --git a/drivers/soc/tegra/tegra-cfg.c b/drivers/soc/tegra/tegra-cfg.c
new file mode 100644
index 000000000000..53d181bff40a
--- /dev/null
+++ b/drivers/soc/tegra/tegra-cfg.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+
+#include <soc/tegra/tegra-cfg.h>
+
+static int tegra_cfg_update_reg_info(struct device_node *cfg_node,
+ const struct tegra_cfg_field_desc *field,
+ struct tegra_cfg_reg *regs,
+ struct tegra_cfg *cfg)
+{
+ int ret;
+ unsigned int k, value = 0;
+
+ ret = of_property_read_u32(cfg_node, field->name, &value);
+ if (ret)
+ return ret;
+
+ /*
+ * Find matching register for this field in register info. Field info
+ * has details of register offset.
+ */
+ for (k = 0; k < cfg->num_regs; ++k) {
+ if (regs[k].offset == field->offset)
+ break;
+ }
+
+ /* If register not found, add new at end of list */
+ if (k == cfg->num_regs) {
+ cfg->num_regs++;
+ regs[k].offset = field->offset;
+ }
+
+ /* add field value to register */
+ value = value << __ffs(field->mask);
+ regs[k].value |= value & field->mask;
+ regs[k].mask |= field->mask;
+
+ return 0;
+}
+
+/*
+ * Initialize config list. Parse config node for properties (register fields).
+ * Get list of configs and value of fields populated in tegra_cfg_desc.
+ * Consolidate field data in reg offset, mask & value format in tegra_cfg.
+ * Repeat for each config and store in tegra_cfg_list.
+ */
+static struct tegra_cfg_list *tegra_cfg_init(struct device *dev,
+ const struct device_node *np,
+ const struct tegra_cfg_desc *cfg_desc)
+{
+ struct device_node *np_cfg = NULL, *child;
+ struct tegra_cfg_reg *regs;
+ struct tegra_cfg_list *list;
+ struct tegra_cfg *cfg;
+ const struct tegra_cfg_field_desc *fields;
+ unsigned int count, i;
+ int ret;
+
+ if (np)
+ np_cfg = of_get_child_by_name(np, "config");
+ if (!np_cfg)
+ return ERR_PTR(-ENODEV);
+
+ count = of_get_child_count(np_cfg);
+ if (count <= 0) {
+ dev_dbg(dev, "Node %s: No child node for config settings\n",
+ np->name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ list = devm_kzalloc(dev, sizeof(*list), GFP_KERNEL);
+ if (!list)
+ return ERR_PTR(-ENOMEM);
+ list->num_cfg = 0;
+ list->cfg = NULL;
+
+ /* allocate mem for all configurations */
+ list->cfg = devm_kcalloc(dev, count, sizeof(*list->cfg),
+ GFP_KERNEL);
+ if (!list->cfg)
+ return ERR_PTR(-ENOMEM);
+
+ fields = cfg_desc->fields;
+ count = 0;
+ /*
+ * Iterate through all configurations.
+ */
+ for_each_available_child_of_node(np_cfg, child) {
+ cfg = &list->cfg[count];
+
+ regs = devm_kcalloc(dev, cfg_desc->num_regs,
+ sizeof(*regs), GFP_KERNEL);
+ if (!regs)
+ return ERR_PTR(-ENOMEM);
+
+ cfg->name = child->name;
+ cfg->regs = regs;
+ cfg->num_regs = 0;
+
+ /* Look for all fields in 'child' config */
+ for (i = 0; i < cfg_desc->num_fields; i++) {
+ ret = tegra_cfg_update_reg_info(child, &fields[i],
+ regs, cfg);
+ if (ret < 0)
+ continue;
+ }
+ count++;
+ }
+
+ list->num_cfg = count;
+
+ return list;
+}
+
+struct tegra_cfg *
+tegra_cfg_get_by_name(struct device *dev,
+ const struct tegra_cfg_list *list,
+ const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < list->num_cfg; ++i) {
+ if (!strcmp(list->cfg[i].name, name))
+ return &list->cfg[i];
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL(tegra_cfg_get_by_name);
+
+struct tegra_cfg_list *tegra_cfg_get(struct device *dev,
+ const struct device_node *np,
+ const struct tegra_cfg_desc *cfg_desc)
+{
+ return tegra_cfg_init(dev, np, cfg_desc);
+}
+EXPORT_SYMBOL(tegra_cfg_get);
diff --git a/include/soc/tegra/tegra-cfg.h b/include/soc/tegra/tegra-cfg.h
new file mode 100644
index 000000000000..ece6f63a83c1
--- /dev/null
+++ b/include/soc/tegra/tegra-cfg.h
@@ -0,0 +1,87 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#ifndef __SOC_TEGRA_CFG_H__
+#define __SOC_TEGRA_CFG_H__
+
+#include <linux/types.h>
+
+/**
+ * Config settings are a list of DT properties holding each field's recommended
+ * value. Field info is held in tegra_cfg_field and tegra_cfg_desc.
+ * Data of all fields in a single register are parsed and stored in
+ * tegra_cfg_reg. Struct tegra_cfg_list contains list of configurations
+ * and each config tegra_cfg contains register list.
+ * Client drivers provide field and register data through tegra_cfg_desc.
+ */
+
+/**
+ * Register field and DT property mapping.
+ * @name: device property name of the field.
+ * @offset: offset of register from base.
+ * @mask: mask of field within register.
+ */
+struct tegra_cfg_field_desc {
+ const char *name;
+ u32 offset;
+ u32 mask;
+};
+
+#define TEGRA_CFG_FIELD(fname, roffset, fmask) \
+{ \
+ .name = fname, \
+ .offset = roffset, \
+ .mask = fmask, \
+}
+
+/**
+ * Configuration setting from controller where it passes the total number of
+ * registers having config, and their register field names.
+ */
+struct tegra_cfg_desc {
+ unsigned int num_regs;
+ unsigned int num_fields;
+ const struct tegra_cfg_field_desc *fields;
+};
+
+/**
+ * Configuration register info generated by combining all field config settings
+ * in device tree of a register.
+ * @offset: offset of register from base.
+ * @mask: generated mask from aggregate of all field settings read from dt.
+ * @value: generated value by combining all field properties read from dt.
+ */
+struct tegra_cfg_reg {
+ u32 offset;
+ u32 mask;
+ u32 value;
+};
+
+/**
+ * Per config info of all registers.
+ */
+struct tegra_cfg {
+ const char *name;
+ unsigned int num_regs;
+ struct tegra_cfg_reg *regs;
+};
+
+/**
+ * Config settings list.
+ */
+struct tegra_cfg_list {
+ unsigned int num_cfg;
+ struct tegra_cfg *cfg;
+};
+
+struct tegra_cfg *
+tegra_cfg_get_by_name(struct device *dev,
+ const struct tegra_cfg_list *list,
+ const char *cfg_name);
+
+struct tegra_cfg_list *tegra_cfg_get(struct device *dev,
+ const struct device_node *np,
+ const struct tegra_cfg_desc *cfg_dev);
+#endif /* __SOC_TEGRA_CFG_H__ */
--
2.43.2