// SPDX-License-Identifier: GPL-2.0-or-later /* * System Control Driver * * Copyright (C) 2012 Freescale Semiconductor, Inc. * Copyright (C) 2012 Linaro Ltd. * * Author: Dong Aisheng */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct platform_driver syscon_driver; static DEFINE_SPINLOCK(syscon_list_slock); static LIST_HEAD(syscon_list); struct syscon { struct device_node *np; struct regmap *regmap; struct reset_control *reset; struct list_head list; }; static const struct regmap_config syscon_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, }; static struct syscon *of_syscon_register(struct device_node *np, bool check_res) { struct clk *clk; struct regmap *regmap; void __iomem *base; u32 reg_io_width; int ret; struct regmap_config syscon_config = syscon_regmap_config; struct resource res; struct reset_control *reset; struct syscon *syscon __free(kfree) = kzalloc(sizeof(*syscon), GFP_KERNEL); if (!syscon) return ERR_PTR(-ENOMEM); if (of_address_to_resource(np, 0, &res)) return ERR_PTR(-ENOMEM); base = of_iomap(np, 0); if (!base) return ERR_PTR(-ENOMEM); /* Parse the device's DT node for an endianness specification */ if (of_property_read_bool(np, "big-endian")) syscon_config.val_format_endian = REGMAP_ENDIAN_BIG; else if (of_property_read_bool(np, "little-endian")) syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; else if (of_property_read_bool(np, "native-endian")) syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE; /* * search for reg-io-width property in DT. If it is not provided, * default to 4 bytes. regmap_init_mmio will return an error if values * are invalid so there is no need to check them here. */ ret = of_property_read_u32(np, "reg-io-width", ®_io_width); if (ret) reg_io_width = 4; ret = of_hwspin_lock_get_id(np, 0); if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) { syscon_config.use_hwlock = true; syscon_config.hwlock_id = ret; syscon_config.hwlock_mode = HWLOCK_IRQSTATE; } else if (ret < 0) { switch (ret) { case -ENOENT: /* Ignore missing hwlock, it's optional. */ break; default: pr_err("Failed to retrieve valid hwlock: %d\n", ret); fallthrough; case -EPROBE_DEFER: goto err_regmap; } } syscon_config.name = kasprintf(GFP_KERNEL, "%pOFn@%pa", np, &res.start); if (!syscon_config.name) { ret = -ENOMEM; goto err_regmap; } syscon_config.reg_stride = reg_io_width; syscon_config.val_bits = reg_io_width * 8; syscon_config.max_register = resource_size(&res) - reg_io_width; if (!syscon_config.max_register) syscon_config.max_register_is_0 = true; regmap = regmap_init_mmio(NULL, base, &syscon_config); kfree(syscon_config.name); if (IS_ERR(regmap)) { pr_err("regmap init failed\n"); ret = PTR_ERR(regmap); goto err_regmap; } if (check_res) { clk = of_clk_get(np, 0); if (IS_ERR(clk)) { ret = PTR_ERR(clk); /* clock is optional */ if (ret != -ENOENT) goto err_clk; } else { ret = regmap_mmio_attach_clk(regmap, clk); if (ret) goto err_attach_clk; } reset = of_reset_control_get_optional_exclusive(np, NULL); if (IS_ERR(reset)) { ret = PTR_ERR(reset); goto err_attach_clk; } ret = reset_control_deassert(reset); if (ret) goto err_reset; } syscon->regmap = regmap; syscon->np = np; spin_lock(&syscon_list_slock); list_add_tail(&syscon->list, &syscon_list); spin_unlock(&syscon_list_slock); return_ptr(syscon); err_reset: reset_control_put(reset); err_attach_clk: if (!IS_ERR(clk)) clk_put(clk); err_clk: regmap_exit(regmap); err_regmap: iounmap(base); return ERR_PTR(ret); } static struct regmap *device_node_get_regmap(struct device_node *np, bool check_res) { struct syscon *entry, *syscon = NULL; spin_lock(&syscon_list_slock); list_for_each_entry(entry, &syscon_list, list) if (entry->np == np) { syscon = entry; break; } spin_unlock(&syscon_list_slock); if (!syscon) syscon = of_syscon_register(np, check_res); if (IS_ERR(syscon)) return ERR_CAST(syscon); return syscon->regmap; } /** * of_syscon_register_regmap() - Register regmap for specified device node * @np: Device tree node * @regmap: Pointer to regmap object * * Register an externally created regmap object with syscon for the specified * device tree node. This regmap will then be returned to client drivers using * the syscon_regmap_lookup_by_phandle() API. * * Return: 0 on success, negative error code on failure. */ int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap) { struct syscon *entry, *syscon = NULL; int ret; if (!np || !regmap) return -EINVAL; syscon = kzalloc(sizeof(*syscon), GFP_KERNEL); if (!syscon) return -ENOMEM; /* check if syscon entry already exists */ spin_lock(&syscon_list_slock); list_for_each_entry(entry, &syscon_list, list) if (entry->np == np) { ret = -EEXIST; goto err_unlock; } syscon->regmap = regmap; syscon->np = np; /* register the regmap in syscon list */ list_add_tail(&syscon->list, &syscon_list); spin_unlock(&syscon_list_slock); return 0; err_unlock: spin_unlock(&syscon_list_slock); kfree(syscon); return ret; } EXPORT_SYMBOL_GPL(of_syscon_register_regmap); struct regmap *device_node_to_regmap(struct device_node *np) { return device_node_get_regmap(np, false); } EXPORT_SYMBOL_GPL(device_node_to_regmap); struct regmap *syscon_node_to_regmap(struct device_node *np) { if (!of_device_is_compatible(np, "syscon")) return ERR_PTR(-EINVAL); return device_node_get_regmap(np, true); } EXPORT_SYMBOL_GPL(syscon_node_to_regmap); struct regmap *syscon_regmap_lookup_by_compatible(const char *s) { struct device_node *syscon_np; struct regmap *regmap; syscon_np = of_find_compatible_node(NULL, NULL, s); if (!syscon_np) return ERR_PTR(-ENODEV); regmap = syscon_node_to_regmap(syscon_np); of_node_put(syscon_np); return regmap; } EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, const char *property) { struct device_node *syscon_np; struct regmap *regmap; if (property) syscon_np = of_parse_phandle(np, property, 0); else syscon_np = np; if (!syscon_np) return ERR_PTR(-ENODEV); regmap = syscon_node_to_regmap(syscon_np); if (property) of_node_put(syscon_np); return regmap; } EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); struct regmap *syscon_regmap_lookup_by_phandle_args(struct device_node *np, const char *property, int arg_count, unsigned int *out_args) { struct device_node *syscon_np; struct of_phandle_args args; struct regmap *regmap; unsigned int index; int rc; rc = of_parse_phandle_with_fixed_args(np, property, arg_count, 0, &args); if (rc) return ERR_PTR(rc); syscon_np = args.np; if (!syscon_np) return ERR_PTR(-ENODEV); regmap = syscon_node_to_regmap(syscon_np); for (index = 0; index < arg_count; index++) out_args[index] = args.args[index]; of_node_put(syscon_np); return regmap; } EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_args); /* * It behaves the same as syscon_regmap_lookup_by_phandle() except where * there is no regmap phandle. In this case, instead of returning -ENODEV, * the function returns NULL. */ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np, const char *property) { struct regmap *regmap; regmap = syscon_regmap_lookup_by_phandle(np, property); if (IS_ERR(regmap) && PTR_ERR(regmap) == -ENODEV) return NULL; return regmap; } EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional); static int syscon_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct syscon_platform_data *pdata = dev_get_platdata(dev); struct syscon *syscon; struct regmap_config syscon_config = syscon_regmap_config; struct resource *res; void __iomem *base; syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL); if (!syscon) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENOENT; base = devm_ioremap(dev, res->start, resource_size(res)); if (!base) return -ENOMEM; syscon_config.max_register = resource_size(res) - 4; if (!syscon_config.max_register) syscon_config.max_register_is_0 = true; if (pdata) syscon_config.name = pdata->label; syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config); if (IS_ERR(syscon->regmap)) { dev_err(dev, "regmap init failed\n"); return PTR_ERR(syscon->regmap); } platform_set_drvdata(pdev, syscon); dev_dbg(dev, "regmap %pR registered\n", res); return 0; } static const struct platform_device_id syscon_ids[] = { { "syscon", }, { } }; static struct platform_driver syscon_driver = { .driver = { .name = "syscon", }, .probe = syscon_probe, .id_table = syscon_ids, }; static int __init syscon_init(void) { return platform_driver_register(&syscon_driver); } postcore_initcall(syscon_init);