![Paul Cercueil](/assets/img/avatar_default.png)
device_node_to_regmap() is exactly like syscon_node_to_regmap(), but it does not check that the node is compatible with "syscon", and won't attach the first clock it finds to the regmap. The rationale behind this, is that one device node with a standard compatible string "foo,bar" can be covered by multiple drivers sharing a regmap, or by a single driver doing all the job without a regmap, but these are implementation details which shouldn't reflect on the devicetree. Signed-off-by: Paul Cercueil <paul@crapouillou.net> Acked-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Paul Burton <paul.burton@mips.com> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: James Hogan <jhogan@kernel.org> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Lee Jones <lee.jones@linaro.org> Cc: Daniel Lezcano <daniel.lezcano@linaro.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Michael Turquette <mturquette@baylibre.com> Cc: Stephen Boyd <sboyd@kernel.org> Cc: Jason Cooper <jason@lakedaemon.net> Cc: Marc Zyngier <marc.zyngier@arm.com> Cc: Rob Herring <robh+dt@kernel.org> Cc: Mark Rutland <mark.rutland@arm.com> Cc: devicetree@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: linux-doc@vger.kernel.org Cc: linux-mips@vger.kernel.org Cc: linux-clk@vger.kernel.org Cc: od@zcrc.me Cc: Mathieu Malaterre <malat@debian.org>
283 lines
6.4 KiB
C
283 lines
6.4 KiB
C
// 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 <dong.aisheng@linaro.org>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/hwspinlock.h>
|
|
#include <linux/io.h>
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_data/syscon.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/slab.h>
|
|
|
|
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 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_clk)
|
|
{
|
|
struct clk *clk;
|
|
struct syscon *syscon;
|
|
struct regmap *regmap;
|
|
void __iomem *base;
|
|
u32 reg_io_width;
|
|
int ret;
|
|
struct regmap_config syscon_config = syscon_regmap_config;
|
|
struct resource res;
|
|
|
|
syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);
|
|
if (!syscon)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (of_address_to_resource(np, 0, &res)) {
|
|
ret = -ENOMEM;
|
|
goto err_map;
|
|
}
|
|
|
|
base = ioremap(res.start, resource_size(&res));
|
|
if (!base) {
|
|
ret = -ENOMEM;
|
|
goto err_map;
|
|
}
|
|
|
|
/* 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);
|
|
/* fall-through */
|
|
case -EPROBE_DEFER:
|
|
goto err_regmap;
|
|
}
|
|
}
|
|
|
|
syscon_config.name = of_node_full_name(np);
|
|
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;
|
|
syscon_config.name = of_node_full_name(np);
|
|
|
|
regmap = regmap_init_mmio(NULL, base, &syscon_config);
|
|
if (IS_ERR(regmap)) {
|
|
pr_err("regmap init failed\n");
|
|
ret = PTR_ERR(regmap);
|
|
goto err_regmap;
|
|
}
|
|
|
|
if (check_clk) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
syscon->regmap = regmap;
|
|
syscon->np = np;
|
|
|
|
spin_lock(&syscon_list_slock);
|
|
list_add_tail(&syscon->list, &syscon_list);
|
|
spin_unlock(&syscon_list_slock);
|
|
|
|
return syscon;
|
|
|
|
err_attach:
|
|
if (!IS_ERR(clk))
|
|
clk_put(clk);
|
|
err_clk:
|
|
regmap_exit(regmap);
|
|
err_regmap:
|
|
iounmap(base);
|
|
err_map:
|
|
kfree(syscon);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static struct regmap *device_node_get_regmap(struct device_node *np,
|
|
bool check_clk)
|
|
{
|
|
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_clk);
|
|
|
|
if (IS_ERR(syscon))
|
|
return ERR_CAST(syscon);
|
|
|
|
return syscon->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);
|
|
of_node_put(syscon_np);
|
|
|
|
return regmap;
|
|
}
|
|
EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle);
|
|
|
|
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 = res->end - res->start - 3;
|
|
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);
|