187d68730SDong Aisheng /* 287d68730SDong Aisheng * System Control Driver 387d68730SDong Aisheng * 487d68730SDong Aisheng * Copyright (C) 2012 Freescale Semiconductor, Inc. 587d68730SDong Aisheng * Copyright (C) 2012 Linaro Ltd. 687d68730SDong Aisheng * 787d68730SDong Aisheng * Author: Dong Aisheng <dong.aisheng@linaro.org> 887d68730SDong Aisheng * 987d68730SDong Aisheng * This program is free software; you can redistribute it and/or modify 1087d68730SDong Aisheng * it under the terms of the GNU General Public License as published by 1187d68730SDong Aisheng * the Free Software Foundation; either version 2 of the License, or 1287d68730SDong Aisheng * (at your option) any later version. 1387d68730SDong Aisheng */ 1487d68730SDong Aisheng 1587d68730SDong Aisheng #include <linux/err.h> 1687d68730SDong Aisheng #include <linux/io.h> 1787d68730SDong Aisheng #include <linux/module.h> 18bdb0066dSPankaj Dubey #include <linux/list.h> 1987d68730SDong Aisheng #include <linux/of.h> 2087d68730SDong Aisheng #include <linux/of_address.h> 2187d68730SDong Aisheng #include <linux/of_platform.h> 2229f9b6cfSPawel Moll #include <linux/platform_data/syscon.h> 2387d68730SDong Aisheng #include <linux/platform_device.h> 2487d68730SDong Aisheng #include <linux/regmap.h> 2575177deeSFabio Estevam #include <linux/mfd/syscon.h> 26bdb0066dSPankaj Dubey #include <linux/slab.h> 2787d68730SDong Aisheng 2887d68730SDong Aisheng static struct platform_driver syscon_driver; 2987d68730SDong Aisheng 30bdb0066dSPankaj Dubey static DEFINE_SPINLOCK(syscon_list_slock); 31bdb0066dSPankaj Dubey static LIST_HEAD(syscon_list); 32bdb0066dSPankaj Dubey 3387d68730SDong Aisheng struct syscon { 34bdb0066dSPankaj Dubey struct device_node *np; 3587d68730SDong Aisheng struct regmap *regmap; 36bdb0066dSPankaj Dubey struct list_head list; 3787d68730SDong Aisheng }; 3887d68730SDong Aisheng 39bdb0066dSPankaj Dubey static struct regmap_config syscon_regmap_config = { 40bdb0066dSPankaj Dubey .reg_bits = 32, 41bdb0066dSPankaj Dubey .val_bits = 32, 42bdb0066dSPankaj Dubey .reg_stride = 4, 43bdb0066dSPankaj Dubey }; 4487d68730SDong Aisheng 45bdb0066dSPankaj Dubey static struct syscon *of_syscon_register(struct device_node *np) 46bdb0066dSPankaj Dubey { 47bdb0066dSPankaj Dubey struct syscon *syscon; 48bdb0066dSPankaj Dubey struct regmap *regmap; 49bdb0066dSPankaj Dubey void __iomem *base; 50bdb0066dSPankaj Dubey int ret; 51bdb0066dSPankaj Dubey struct regmap_config syscon_config = syscon_regmap_config; 52bdb0066dSPankaj Dubey 53bdb0066dSPankaj Dubey if (!of_device_is_compatible(np, "syscon")) 54bdb0066dSPankaj Dubey return ERR_PTR(-EINVAL); 55bdb0066dSPankaj Dubey 56bdb0066dSPankaj Dubey syscon = kzalloc(sizeof(*syscon), GFP_KERNEL); 57bdb0066dSPankaj Dubey if (!syscon) 58bdb0066dSPankaj Dubey return ERR_PTR(-ENOMEM); 59bdb0066dSPankaj Dubey 60bdb0066dSPankaj Dubey base = of_iomap(np, 0); 61bdb0066dSPankaj Dubey if (!base) { 62bdb0066dSPankaj Dubey ret = -ENOMEM; 63bdb0066dSPankaj Dubey goto err_map; 64bdb0066dSPankaj Dubey } 65bdb0066dSPankaj Dubey 66bdb0066dSPankaj Dubey /* Parse the device's DT node for an endianness specification */ 67bdb0066dSPankaj Dubey if (of_property_read_bool(np, "big-endian")) 68bdb0066dSPankaj Dubey syscon_config.val_format_endian = REGMAP_ENDIAN_BIG; 69bdb0066dSPankaj Dubey else if (of_property_read_bool(np, "little-endian")) 70bdb0066dSPankaj Dubey syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; 71bdb0066dSPankaj Dubey 72bdb0066dSPankaj Dubey regmap = regmap_init_mmio(NULL, base, &syscon_config); 73bdb0066dSPankaj Dubey if (IS_ERR(regmap)) { 74bdb0066dSPankaj Dubey pr_err("regmap init failed\n"); 75bdb0066dSPankaj Dubey ret = PTR_ERR(regmap); 76bdb0066dSPankaj Dubey goto err_regmap; 77bdb0066dSPankaj Dubey } 78bdb0066dSPankaj Dubey 79bdb0066dSPankaj Dubey syscon->regmap = regmap; 80bdb0066dSPankaj Dubey syscon->np = np; 81bdb0066dSPankaj Dubey 82bdb0066dSPankaj Dubey spin_lock(&syscon_list_slock); 83bdb0066dSPankaj Dubey list_add_tail(&syscon->list, &syscon_list); 84bdb0066dSPankaj Dubey spin_unlock(&syscon_list_slock); 85bdb0066dSPankaj Dubey 86bdb0066dSPankaj Dubey return syscon; 87bdb0066dSPankaj Dubey 88bdb0066dSPankaj Dubey err_regmap: 89bdb0066dSPankaj Dubey iounmap(base); 90bdb0066dSPankaj Dubey err_map: 91bdb0066dSPankaj Dubey kfree(syscon); 92bdb0066dSPankaj Dubey return ERR_PTR(ret); 9387d68730SDong Aisheng } 9487d68730SDong Aisheng 9587d68730SDong Aisheng struct regmap *syscon_node_to_regmap(struct device_node *np) 9687d68730SDong Aisheng { 97bdb0066dSPankaj Dubey struct syscon *entry, *syscon = NULL; 9887d68730SDong Aisheng 99bdb0066dSPankaj Dubey spin_lock(&syscon_list_slock); 10087d68730SDong Aisheng 101bdb0066dSPankaj Dubey list_for_each_entry(entry, &syscon_list, list) 102bdb0066dSPankaj Dubey if (entry->np == np) { 103bdb0066dSPankaj Dubey syscon = entry; 104bdb0066dSPankaj Dubey break; 105bdb0066dSPankaj Dubey } 106bdb0066dSPankaj Dubey 107bdb0066dSPankaj Dubey spin_unlock(&syscon_list_slock); 108bdb0066dSPankaj Dubey 109bdb0066dSPankaj Dubey if (!syscon) 110bdb0066dSPankaj Dubey syscon = of_syscon_register(np); 111bdb0066dSPankaj Dubey 112bdb0066dSPankaj Dubey if (IS_ERR(syscon)) 113bdb0066dSPankaj Dubey return ERR_CAST(syscon); 11487d68730SDong Aisheng 11587d68730SDong Aisheng return syscon->regmap; 11687d68730SDong Aisheng } 11787d68730SDong Aisheng EXPORT_SYMBOL_GPL(syscon_node_to_regmap); 11887d68730SDong Aisheng 11987d68730SDong Aisheng struct regmap *syscon_regmap_lookup_by_compatible(const char *s) 12087d68730SDong Aisheng { 12187d68730SDong Aisheng struct device_node *syscon_np; 12287d68730SDong Aisheng struct regmap *regmap; 12387d68730SDong Aisheng 12487d68730SDong Aisheng syscon_np = of_find_compatible_node(NULL, NULL, s); 12587d68730SDong Aisheng if (!syscon_np) 12687d68730SDong Aisheng return ERR_PTR(-ENODEV); 12787d68730SDong Aisheng 12887d68730SDong Aisheng regmap = syscon_node_to_regmap(syscon_np); 12987d68730SDong Aisheng of_node_put(syscon_np); 13087d68730SDong Aisheng 13187d68730SDong Aisheng return regmap; 13287d68730SDong Aisheng } 13387d68730SDong Aisheng EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible); 13487d68730SDong Aisheng 1355ab3a89aSAlexander Shiyan static int syscon_match_pdevname(struct device *dev, void *data) 1365ab3a89aSAlexander Shiyan { 1375ab3a89aSAlexander Shiyan return !strcmp(dev_name(dev), (const char *)data); 1385ab3a89aSAlexander Shiyan } 1395ab3a89aSAlexander Shiyan 1405ab3a89aSAlexander Shiyan struct regmap *syscon_regmap_lookup_by_pdevname(const char *s) 1415ab3a89aSAlexander Shiyan { 1425ab3a89aSAlexander Shiyan struct device *dev; 1435ab3a89aSAlexander Shiyan struct syscon *syscon; 1445ab3a89aSAlexander Shiyan 1455ab3a89aSAlexander Shiyan dev = driver_find_device(&syscon_driver.driver, NULL, (void *)s, 1465ab3a89aSAlexander Shiyan syscon_match_pdevname); 1475ab3a89aSAlexander Shiyan if (!dev) 1485ab3a89aSAlexander Shiyan return ERR_PTR(-EPROBE_DEFER); 1495ab3a89aSAlexander Shiyan 1505ab3a89aSAlexander Shiyan syscon = dev_get_drvdata(dev); 1515ab3a89aSAlexander Shiyan 1525ab3a89aSAlexander Shiyan return syscon->regmap; 1535ab3a89aSAlexander Shiyan } 1545ab3a89aSAlexander Shiyan EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_pdevname); 1555ab3a89aSAlexander Shiyan 15687d68730SDong Aisheng struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, 15787d68730SDong Aisheng const char *property) 15887d68730SDong Aisheng { 15987d68730SDong Aisheng struct device_node *syscon_np; 16087d68730SDong Aisheng struct regmap *regmap; 16187d68730SDong Aisheng 16245330bb4SPankaj Dubey if (property) 16387d68730SDong Aisheng syscon_np = of_parse_phandle(np, property, 0); 16445330bb4SPankaj Dubey else 16545330bb4SPankaj Dubey syscon_np = np; 16645330bb4SPankaj Dubey 16787d68730SDong Aisheng if (!syscon_np) 16887d68730SDong Aisheng return ERR_PTR(-ENODEV); 16987d68730SDong Aisheng 17087d68730SDong Aisheng regmap = syscon_node_to_regmap(syscon_np); 17187d68730SDong Aisheng of_node_put(syscon_np); 17287d68730SDong Aisheng 17387d68730SDong Aisheng return regmap; 17487d68730SDong Aisheng } 17587d68730SDong Aisheng EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle); 17687d68730SDong Aisheng 177f791be49SBill Pemberton static int syscon_probe(struct platform_device *pdev) 17887d68730SDong Aisheng { 17987d68730SDong Aisheng struct device *dev = &pdev->dev; 18029f9b6cfSPawel Moll struct syscon_platform_data *pdata = dev_get_platdata(dev); 18187d68730SDong Aisheng struct syscon *syscon; 1825ab3a89aSAlexander Shiyan struct resource *res; 183f10111ccSAlexander Shiyan void __iomem *base; 18487d68730SDong Aisheng 1855ab3a89aSAlexander Shiyan syscon = devm_kzalloc(dev, sizeof(*syscon), GFP_KERNEL); 18687d68730SDong Aisheng if (!syscon) 18787d68730SDong Aisheng return -ENOMEM; 18887d68730SDong Aisheng 1895ab3a89aSAlexander Shiyan res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1905ab3a89aSAlexander Shiyan if (!res) 1915ab3a89aSAlexander Shiyan return -ENOENT; 1925ab3a89aSAlexander Shiyan 193f10111ccSAlexander Shiyan base = devm_ioremap(dev, res->start, resource_size(res)); 194f10111ccSAlexander Shiyan if (!base) 1955ab3a89aSAlexander Shiyan return -ENOMEM; 19687d68730SDong Aisheng 1975ab3a89aSAlexander Shiyan syscon_regmap_config.max_register = res->end - res->start - 3; 19829f9b6cfSPawel Moll if (pdata) 19929f9b6cfSPawel Moll syscon_regmap_config.name = pdata->label; 200f10111ccSAlexander Shiyan syscon->regmap = devm_regmap_init_mmio(dev, base, 20187d68730SDong Aisheng &syscon_regmap_config); 20287d68730SDong Aisheng if (IS_ERR(syscon->regmap)) { 20387d68730SDong Aisheng dev_err(dev, "regmap init failed\n"); 20487d68730SDong Aisheng return PTR_ERR(syscon->regmap); 20587d68730SDong Aisheng } 20687d68730SDong Aisheng 20787d68730SDong Aisheng platform_set_drvdata(pdev, syscon); 20887d68730SDong Aisheng 20938d8974eSAlexander Shiyan dev_dbg(dev, "regmap %pR registered\n", res); 21087d68730SDong Aisheng 21187d68730SDong Aisheng return 0; 21287d68730SDong Aisheng } 21387d68730SDong Aisheng 2145ab3a89aSAlexander Shiyan static const struct platform_device_id syscon_ids[] = { 2155ab3a89aSAlexander Shiyan { "syscon", }, 2165ab3a89aSAlexander Shiyan { } 2175ab3a89aSAlexander Shiyan }; 21887d68730SDong Aisheng 21987d68730SDong Aisheng static struct platform_driver syscon_driver = { 22087d68730SDong Aisheng .driver = { 22187d68730SDong Aisheng .name = "syscon", 22287d68730SDong Aisheng .owner = THIS_MODULE, 22387d68730SDong Aisheng }, 22487d68730SDong Aisheng .probe = syscon_probe, 2255ab3a89aSAlexander Shiyan .id_table = syscon_ids, 22687d68730SDong Aisheng }; 22787d68730SDong Aisheng 22887d68730SDong Aisheng static int __init syscon_init(void) 22987d68730SDong Aisheng { 23087d68730SDong Aisheng return platform_driver_register(&syscon_driver); 23187d68730SDong Aisheng } 23287d68730SDong Aisheng postcore_initcall(syscon_init); 23387d68730SDong Aisheng 23487d68730SDong Aisheng static void __exit syscon_exit(void) 23587d68730SDong Aisheng { 23687d68730SDong Aisheng platform_driver_unregister(&syscon_driver); 23787d68730SDong Aisheng } 23887d68730SDong Aisheng module_exit(syscon_exit); 23987d68730SDong Aisheng 24087d68730SDong Aisheng MODULE_AUTHOR("Dong Aisheng <dong.aisheng@linaro.org>"); 24187d68730SDong Aisheng MODULE_DESCRIPTION("System Control driver"); 24287d68730SDong Aisheng MODULE_LICENSE("GPL v2"); 243