linux/drivers/mfd/syscon.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * System Control Driver
   4 *
   5 * Copyright (C) 2012 Freescale Semiconductor, Inc.
   6 * Copyright (C) 2012 Linaro Ltd.
   7 *
   8 * Author: Dong Aisheng <dong.aisheng@linaro.org>
   9 */
  10
  11#include <linux/clk.h>
  12#include <linux/err.h>
  13#include <linux/hwspinlock.h>
  14#include <linux/io.h>
  15#include <linux/init.h>
  16#include <linux/list.h>
  17#include <linux/of.h>
  18#include <linux/of_address.h>
  19#include <linux/of_platform.h>
  20#include <linux/platform_data/syscon.h>
  21#include <linux/platform_device.h>
  22#include <linux/regmap.h>
  23#include <linux/mfd/syscon.h>
  24#include <linux/slab.h>
  25
  26static struct platform_driver syscon_driver;
  27
  28static DEFINE_SPINLOCK(syscon_list_slock);
  29static LIST_HEAD(syscon_list);
  30
  31struct syscon {
  32        struct device_node *np;
  33        struct regmap *regmap;
  34        struct list_head list;
  35};
  36
  37static const struct regmap_config syscon_regmap_config = {
  38        .reg_bits = 32,
  39        .val_bits = 32,
  40        .reg_stride = 4,
  41};
  42
  43static struct syscon *of_syscon_register(struct device_node *np, bool check_clk)
  44{
  45        struct clk *clk;
  46        struct syscon *syscon;
  47        struct regmap *regmap;
  48        void __iomem *base;
  49        u32 reg_io_width;
  50        int ret;
  51        struct regmap_config syscon_config = syscon_regmap_config;
  52        struct resource res;
  53
  54        syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);
  55        if (!syscon)
  56                return ERR_PTR(-ENOMEM);
  57
  58        if (of_address_to_resource(np, 0, &res)) {
  59                ret = -ENOMEM;
  60                goto err_map;
  61        }
  62
  63        base = of_iomap(np, 0);
  64        if (!base) {
  65                ret = -ENOMEM;
  66                goto err_map;
  67        }
  68
  69        /* Parse the device's DT node for an endianness specification */
  70        if (of_property_read_bool(np, "big-endian"))
  71                syscon_config.val_format_endian = REGMAP_ENDIAN_BIG;
  72        else if (of_property_read_bool(np, "little-endian"))
  73                syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE;
  74        else if (of_property_read_bool(np, "native-endian"))
  75                syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE;
  76
  77        /*
  78         * search for reg-io-width property in DT. If it is not provided,
  79         * default to 4 bytes. regmap_init_mmio will return an error if values
  80         * are invalid so there is no need to check them here.
  81         */
  82        ret = of_property_read_u32(np, "reg-io-width", &reg_io_width);
  83        if (ret)
  84                reg_io_width = 4;
  85
  86        ret = of_hwspin_lock_get_id(np, 0);
  87        if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) {
  88                syscon_config.use_hwlock = true;
  89                syscon_config.hwlock_id = ret;
  90                syscon_config.hwlock_mode = HWLOCK_IRQSTATE;
  91        } else if (ret < 0) {
  92                switch (ret) {
  93                case -ENOENT:
  94                        /* Ignore missing hwlock, it's optional. */
  95                        break;
  96                default:
  97                        pr_err("Failed to retrieve valid hwlock: %d\n", ret);
  98                        fallthrough;
  99                case -EPROBE_DEFER:
 100                        goto err_regmap;
 101                }
 102        }
 103
 104        syscon_config.name = kasprintf(GFP_KERNEL, "%pOFn@%llx", np,
 105                                       (u64)res.start);
 106        syscon_config.reg_stride = reg_io_width;
 107        syscon_config.val_bits = reg_io_width * 8;
 108        syscon_config.max_register = resource_size(&res) - reg_io_width;
 109
 110        regmap = regmap_init_mmio(NULL, base, &syscon_config);
 111        kfree(syscon_config.name);
 112        if (IS_ERR(regmap)) {
 113                pr_err("regmap init failed\n");
 114                ret = PTR_ERR(regmap);
 115                goto err_regmap;
 116        }
 117
 118        if (check_clk) {
 119                clk = of_clk_get(np, 0);
 120                if (IS_ERR(clk)) {
 121                        ret = PTR_ERR(clk);
 122                        /* clock is optional */
 123                        if (ret != -ENOENT)
 124                                goto err_clk;
 125                } else {
 126                        ret = regmap_mmio_attach_clk(regmap, clk);
 127                        if (ret)
 128                                goto err_attach;
 129                }
 130        }
 131
 132        syscon->regmap = regmap;
 133        syscon->np = np;
 134
 135        spin_lock(&syscon_list_slock);
 136        list_add_tail(&syscon->list, &syscon_list);
 137        spin_unlock(&syscon_list_slock);
 138
 139        return syscon;
 140
 141err_attach:
 142        if (!IS_ERR(clk))
 143                clk_put(clk);
 144err_clk:
 145        regmap_exit(regmap);
 146err_regmap:
 147        iounmap(base);
 148err_map:
 149        kfree(syscon);
 150        return ERR_PTR(ret);
 151}
 152
 153static struct regmap *device_node_get_regmap(struct device_node *np,
 154                                             bool check_clk)
 155{
 156        struct syscon *entry, *syscon = NULL;
 157
 158        spin_lock(&syscon_list_slock);
 159
 160        list_for_each_entry(entry, &syscon_list, list)
 161                if (entry->np == np) {
 162                        syscon = entry;
 163                        break;
 164                }
 165
 166        spin_unlock(&syscon_list_slock);
 167
 168        if (!syscon)
 169                syscon = of_syscon_register(np, check_clk);
 170
 171        if (IS_ERR(syscon))
 172                return ERR_CAST(syscon);
 173
 174        return syscon->regmap;
 175}
 176
 177struct regmap *device_node_to_regmap(struct device_node *np)
 178{
 179        return device_node_get_regmap(np, false);
 180}
 181EXPORT_SYMBOL_GPL(device_node_to_regmap);
 182
 183struct regmap *syscon_node_to_regmap(struct device_node *np)
 184{
 185        if (!of_device_is_compatible(np, "syscon"))
 186                return ERR_PTR(-EINVAL);
 187
 188        return device_node_get_regmap(np, true);
 189}
 190EXPORT_SYMBOL_GPL(syscon_node_to_regmap);
 191
 192struct regmap *syscon_regmap_lookup_by_compatible(const char *s)
 193{
 194        struct device_node *syscon_np;
 195        struct regmap *regmap;
 196
 197        syscon_np = of_find_compatible_node(NULL, NULL, s);
 198        if (!syscon_np)
 199                return ERR_PTR(-ENODEV);
 200
 201        regmap = syscon_node_to_regmap(syscon_np);
 202        of_node_put(syscon_np);
 203
 204        return regmap;
 205}
 206EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible);
 207
 208struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np,
 209                                        const char *property)
 210{
 211        struct device_node *syscon_np;
 212        struct regmap *regmap;
 213
 214        if (property)
 215                syscon_np = of_parse_phandle(np, property, 0);
 216        else
 217                syscon_np = np;
 218
 219        if (!syscon_np)
 220                return ERR_PTR(-ENODEV);
 221
 222        regmap = syscon_node_to_regmap(syscon_np);
 223        of_node_put(syscon_np);
 224
 225        return regmap;
 226}
 227EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle);
 228
 229struct regmap *syscon_regmap_lookup_by_phandle_args(struct device_node *np,
 230                                        const char *property,
 231                                        int arg_count,
 232                                        unsigned int *out_args)
 233{
 234        struct device_node *syscon_np;
 235        struct of_phandle_args args;
 236        struct regmap *regmap;
 237        unsigned int index;
 238        int rc;
 239
 240        rc = of_parse_phandle_with_fixed_args(np, property, arg_count,
 241                        0, &args);
 242        if (rc)
 243                return ERR_PTR(rc);
 244
 245        syscon_np = args.np;
 246        if (!syscon_np)
 247                return ERR_PTR(-ENODEV);
 248
 249        regmap = syscon_node_to_regmap(syscon_np);
 250        for (index = 0; index < arg_count; index++)
 251                out_args[index] = args.args[index];
 252        of_node_put(syscon_np);
 253
 254        return regmap;
 255}
 256EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_args);
 257
 258/*
 259 * It behaves the same as syscon_regmap_lookup_by_phandle() except where
 260 * there is no regmap phandle. In this case, instead of returning -ENODEV,
 261 * the function returns NULL.
 262 */
 263struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,
 264                                        const char *property)
 265{
 266        struct regmap *regmap;
 267
 268        regmap = syscon_regmap_lookup_by_phandle(np, property);
 269        if (IS_ERR(regmap) && PTR_ERR(regmap) == -ENODEV)
 270                return NULL;
 271
 272        return regmap;
 273}
 274EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional);
 275
 276static int syscon_probe(struct platform_device *pdev)
 277{
 278        struct device *dev = &pdev->dev;
 279        struct syscon_platform_data *pdata = dev_get_platdata(dev);
 280        struct syscon *syscon;
 281        struct regmap_config syscon_config = syscon_regmap_config;
 282        struct resource *res;
 283        void __iomem *base;
 284
 285        syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL);
 286        if (!syscon)
 287                return -ENOMEM;
 288
 289        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 290        if (!res)
 291                return -ENOENT;
 292
 293        base = devm_ioremap(dev, res->start, resource_size(res));
 294        if (!base)
 295                return -ENOMEM;
 296
 297        syscon_config.max_register = resource_size(res) - 4;
 298        if (pdata)
 299                syscon_config.name = pdata->label;
 300        syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config);
 301        if (IS_ERR(syscon->regmap)) {
 302                dev_err(dev, "regmap init failed\n");
 303                return PTR_ERR(syscon->regmap);
 304        }
 305
 306        platform_set_drvdata(pdev, syscon);
 307
 308        dev_dbg(dev, "regmap %pR registered\n", res);
 309
 310        return 0;
 311}
 312
 313static const struct platform_device_id syscon_ids[] = {
 314        { "syscon", },
 315        { }
 316};
 317
 318static struct platform_driver syscon_driver = {
 319        .driver = {
 320                .name = "syscon",
 321        },
 322        .probe          = syscon_probe,
 323        .id_table       = syscon_ids,
 324};
 325
 326static int __init syscon_init(void)
 327{
 328        return platform_driver_register(&syscon_driver);
 329}
 330postcore_initcall(syscon_init);
 331