brintos

brintos / linux-shallow public Read only

0
0
Text · 9.1 KiB · 2ce15f6 Raw
393 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * System Control Driver4 *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/cleanup.h>12#include <linux/clk.h>13#include <linux/err.h>14#include <linux/hwspinlock.h>15#include <linux/io.h>16#include <linux/init.h>17#include <linux/list.h>18#include <linux/of.h>19#include <linux/of_address.h>20#include <linux/of_platform.h>21#include <linux/platform_data/syscon.h>22#include <linux/platform_device.h>23#include <linux/regmap.h>24#include <linux/reset.h>25#include <linux/mfd/syscon.h>26#include <linux/slab.h>27 28static struct platform_driver syscon_driver;29 30static DEFINE_SPINLOCK(syscon_list_slock);31static LIST_HEAD(syscon_list);32 33struct syscon {34	struct device_node *np;35	struct regmap *regmap;36	struct reset_control *reset;37	struct list_head list;38};39 40static const struct regmap_config syscon_regmap_config = {41	.reg_bits = 32,42	.val_bits = 32,43	.reg_stride = 4,44};45 46static struct syscon *of_syscon_register(struct device_node *np, bool check_res)47{48	struct clk *clk;49	struct regmap *regmap;50	void __iomem *base;51	u32 reg_io_width;52	int ret;53	struct regmap_config syscon_config = syscon_regmap_config;54	struct resource res;55	struct reset_control *reset;56 57	struct syscon *syscon __free(kfree) = kzalloc(sizeof(*syscon), GFP_KERNEL);58	if (!syscon)59		return ERR_PTR(-ENOMEM);60 61	if (of_address_to_resource(np, 0, &res))62		return ERR_PTR(-ENOMEM);63 64	base = of_iomap(np, 0);65	if (!base)66		return ERR_PTR(-ENOMEM);67 68	/* Parse the device's DT node for an endianness specification */69	if (of_property_read_bool(np, "big-endian"))70		syscon_config.val_format_endian = REGMAP_ENDIAN_BIG;71	else if (of_property_read_bool(np, "little-endian"))72		syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE;73	else if (of_property_read_bool(np, "native-endian"))74		syscon_config.val_format_endian = REGMAP_ENDIAN_NATIVE;75 76	/*77	 * search for reg-io-width property in DT. If it is not provided,78	 * default to 4 bytes. regmap_init_mmio will return an error if values79	 * are invalid so there is no need to check them here.80	 */81	ret = of_property_read_u32(np, "reg-io-width", &reg_io_width);82	if (ret)83		reg_io_width = 4;84 85	ret = of_hwspin_lock_get_id(np, 0);86	if (ret > 0 || (IS_ENABLED(CONFIG_HWSPINLOCK) && ret == 0)) {87		syscon_config.use_hwlock = true;88		syscon_config.hwlock_id = ret;89		syscon_config.hwlock_mode = HWLOCK_IRQSTATE;90	} else if (ret < 0) {91		switch (ret) {92		case -ENOENT:93			/* Ignore missing hwlock, it's optional. */94			break;95		default:96			pr_err("Failed to retrieve valid hwlock: %d\n", ret);97			fallthrough;98		case -EPROBE_DEFER:99			goto err_regmap;100		}101	}102 103	syscon_config.name = kasprintf(GFP_KERNEL, "%pOFn@%pa", np, &res.start);104	if (!syscon_config.name) {105		ret = -ENOMEM;106		goto err_regmap;107	}108	syscon_config.reg_stride = reg_io_width;109	syscon_config.val_bits = reg_io_width * 8;110	syscon_config.max_register = resource_size(&res) - reg_io_width;111 112	regmap = regmap_init_mmio(NULL, base, &syscon_config);113	kfree(syscon_config.name);114	if (IS_ERR(regmap)) {115		pr_err("regmap init failed\n");116		ret = PTR_ERR(regmap);117		goto err_regmap;118	}119 120	if (check_res) {121		clk = of_clk_get(np, 0);122		if (IS_ERR(clk)) {123			ret = PTR_ERR(clk);124			/* clock is optional */125			if (ret != -ENOENT)126				goto err_clk;127		} else {128			ret = regmap_mmio_attach_clk(regmap, clk);129			if (ret)130				goto err_attach_clk;131		}132 133		reset = of_reset_control_get_optional_exclusive(np, NULL);134		if (IS_ERR(reset)) {135			ret = PTR_ERR(reset);136			goto err_attach_clk;137		}138 139		ret = reset_control_deassert(reset);140		if (ret)141			goto err_reset;142	}143 144	syscon->regmap = regmap;145	syscon->np = np;146 147	spin_lock(&syscon_list_slock);148	list_add_tail(&syscon->list, &syscon_list);149	spin_unlock(&syscon_list_slock);150 151	return_ptr(syscon);152 153err_reset:154	reset_control_put(reset);155err_attach_clk:156	if (!IS_ERR(clk))157		clk_put(clk);158err_clk:159	regmap_exit(regmap);160err_regmap:161	iounmap(base);162	return ERR_PTR(ret);163}164 165static struct regmap *device_node_get_regmap(struct device_node *np,166					     bool check_res)167{168	struct syscon *entry, *syscon = NULL;169 170	spin_lock(&syscon_list_slock);171 172	list_for_each_entry(entry, &syscon_list, list)173		if (entry->np == np) {174			syscon = entry;175			break;176		}177 178	spin_unlock(&syscon_list_slock);179 180	if (!syscon)181		syscon = of_syscon_register(np, check_res);182 183	if (IS_ERR(syscon))184		return ERR_CAST(syscon);185 186	return syscon->regmap;187}188 189/**190 * of_syscon_register_regmap() - Register regmap for specified device node191 * @np: Device tree node192 * @regmap: Pointer to regmap object193 *194 * Register an externally created regmap object with syscon for the specified195 * device tree node. This regmap will then be returned to client drivers using196 * the syscon_regmap_lookup_by_phandle() API.197 *198 * Return: 0 on success, negative error code on failure.199 */200int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)201{202	struct syscon *entry, *syscon = NULL;203	int ret;204 205	if (!np || !regmap)206		return -EINVAL;207 208	syscon = kzalloc(sizeof(*syscon), GFP_KERNEL);209	if (!syscon)210		return -ENOMEM;211 212	/* check if syscon entry already exists */213	spin_lock(&syscon_list_slock);214 215	list_for_each_entry(entry, &syscon_list, list)216		if (entry->np == np) {217			ret = -EEXIST;218			goto err_unlock;219		}220 221	syscon->regmap = regmap;222	syscon->np = np;223 224	/* register the regmap in syscon list */225	list_add_tail(&syscon->list, &syscon_list);226	spin_unlock(&syscon_list_slock);227 228	return 0;229 230err_unlock:231	spin_unlock(&syscon_list_slock);232	kfree(syscon);233	return ret;234}235EXPORT_SYMBOL_GPL(of_syscon_register_regmap);236 237struct regmap *device_node_to_regmap(struct device_node *np)238{239	return device_node_get_regmap(np, false);240}241EXPORT_SYMBOL_GPL(device_node_to_regmap);242 243struct regmap *syscon_node_to_regmap(struct device_node *np)244{245	if (!of_device_is_compatible(np, "syscon"))246		return ERR_PTR(-EINVAL);247 248	return device_node_get_regmap(np, true);249}250EXPORT_SYMBOL_GPL(syscon_node_to_regmap);251 252struct regmap *syscon_regmap_lookup_by_compatible(const char *s)253{254	struct device_node *syscon_np;255	struct regmap *regmap;256 257	syscon_np = of_find_compatible_node(NULL, NULL, s);258	if (!syscon_np)259		return ERR_PTR(-ENODEV);260 261	regmap = syscon_node_to_regmap(syscon_np);262	of_node_put(syscon_np);263 264	return regmap;265}266EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible);267 268struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np,269					const char *property)270{271	struct device_node *syscon_np;272	struct regmap *regmap;273 274	if (property)275		syscon_np = of_parse_phandle(np, property, 0);276	else277		syscon_np = np;278 279	if (!syscon_np)280		return ERR_PTR(-ENODEV);281 282	regmap = syscon_node_to_regmap(syscon_np);283 284	if (property)285		of_node_put(syscon_np);286 287	return regmap;288}289EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle);290 291struct regmap *syscon_regmap_lookup_by_phandle_args(struct device_node *np,292					const char *property,293					int arg_count,294					unsigned int *out_args)295{296	struct device_node *syscon_np;297	struct of_phandle_args args;298	struct regmap *regmap;299	unsigned int index;300	int rc;301 302	rc = of_parse_phandle_with_fixed_args(np, property, arg_count,303			0, &args);304	if (rc)305		return ERR_PTR(rc);306 307	syscon_np = args.np;308	if (!syscon_np)309		return ERR_PTR(-ENODEV);310 311	regmap = syscon_node_to_regmap(syscon_np);312	for (index = 0; index < arg_count; index++)313		out_args[index] = args.args[index];314	of_node_put(syscon_np);315 316	return regmap;317}318EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_args);319 320/*321 * It behaves the same as syscon_regmap_lookup_by_phandle() except where322 * there is no regmap phandle. In this case, instead of returning -ENODEV,323 * the function returns NULL.324 */325struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,326					const char *property)327{328	struct regmap *regmap;329 330	regmap = syscon_regmap_lookup_by_phandle(np, property);331	if (IS_ERR(regmap) && PTR_ERR(regmap) == -ENODEV)332		return NULL;333 334	return regmap;335}336EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional);337 338static int syscon_probe(struct platform_device *pdev)339{340	struct device *dev = &pdev->dev;341	struct syscon_platform_data *pdata = dev_get_platdata(dev);342	struct syscon *syscon;343	struct regmap_config syscon_config = syscon_regmap_config;344	struct resource *res;345	void __iomem *base;346 347	syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL);348	if (!syscon)349		return -ENOMEM;350 351	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);352	if (!res)353		return -ENOENT;354 355	base = devm_ioremap(dev, res->start, resource_size(res));356	if (!base)357		return -ENOMEM;358 359	syscon_config.max_register = resource_size(res) - 4;360	if (pdata)361		syscon_config.name = pdata->label;362	syscon->regmap = devm_regmap_init_mmio(dev, base, &syscon_config);363	if (IS_ERR(syscon->regmap)) {364		dev_err(dev, "regmap init failed\n");365		return PTR_ERR(syscon->regmap);366	}367 368	platform_set_drvdata(pdev, syscon);369 370	dev_dbg(dev, "regmap %pR registered\n", res);371 372	return 0;373}374 375static const struct platform_device_id syscon_ids[] = {376	{ "syscon", },377	{ }378};379 380static struct platform_driver syscon_driver = {381	.driver = {382		.name = "syscon",383	},384	.probe		= syscon_probe,385	.id_table	= syscon_ids,386};387 388static int __init syscon_init(void)389{390	return platform_driver_register(&syscon_driver);391}392postcore_initcall(syscon_init);393