1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (C) 2015 Google, Inc 4 * Written by Simon Glass <sjg@chromium.org> 5 */ 6 7#define LOG_CATEGORY UCLASS_SYSCON 8 9#include <log.h> 10#include <syscon.h> 11#include <dm.h> 12#include <errno.h> 13#include <regmap.h> 14#include <dm/device-internal.h> 15#include <dm/device_compat.h> 16#include <dm/lists.h> 17#include <dm/root.h> 18#include <linux/err.h> 19 20/* 21 * Caution: 22 * This API requires the given device has already been bound to the syscon 23 * driver. For example, 24 * 25 * compatible = "syscon", "simple-mfd"; 26 * 27 * works, but 28 * 29 * compatible = "simple-mfd", "syscon"; 30 * 31 * does not. The behavior is different from Linux. 32 */ 33struct regmap *syscon_get_regmap(struct udevice *dev) 34{ 35 struct syscon_uc_info *priv; 36 37 if (device_get_uclass_id(dev) != UCLASS_SYSCON) 38 return ERR_PTR(-ENOEXEC); 39 priv = dev_get_uclass_priv(dev); 40 return priv->regmap; 41} 42 43static int syscon_pre_probe(struct udevice *dev) 44{ 45 struct syscon_uc_info *priv = dev_get_uclass_priv(dev); 46 47 /* Special case for PCI devices, which don't have a regmap */ 48 if (device_get_uclass_id(dev->parent) == UCLASS_PCI) 49 return 0; 50 51#if CONFIG_IS_ENABLED(OF_PLATDATA) 52 /* 53 * With OF_PLATDATA we really have no way of knowing the format of 54 * the device-specific platform data. So we assume that it starts with 55 * a 'reg' member that holds a single address and size. Drivers 56 * using OF_PLATDATA will need to ensure that this is true. In case of 57 * odd reg structures other then the syscon_base_plat structure 58 * below the regmap must be defined in the individual syscon driver. 59 */ 60 struct syscon_base_plat { 61 phys_addr_t reg[2]; 62 }; 63 64 struct syscon_base_plat *plat = dev_get_plat(dev); 65 66 /* 67 * Return if the regmap is already defined in the individual 68 * syscon driver. 69 */ 70 if (priv->regmap) 71 return 0; 72 73 return regmap_init_mem_plat(dev, plat->reg, sizeof(plat->reg[0]), 74 ARRAY_SIZE(plat->reg) / 2, &priv->regmap); 75#else 76 return regmap_init_mem(dev_ofnode(dev), &priv->regmap); 77#endif 78} 79 80static int syscon_probe_by_ofnode(ofnode node, struct udevice **devp) 81{ 82 struct udevice *dev, *parent; 83 int ret; 84 85 /* found node with "syscon" compatible, not bounded to SYSCON UCLASS */ 86 if (!ofnode_device_is_compatible(node, "syscon")) { 87 log_debug("invalid compatible for syscon device\n"); 88 return -EINVAL; 89 } 90 91 /* bound to driver with same ofnode or to root if not found */ 92 if (device_find_global_by_ofnode(node, &parent)) 93 parent = dm_root(); 94 95 /* force bound to syscon class */ 96 ret = device_bind_driver_to_node(parent, "syscon", 97 ofnode_get_name(node), 98 node, &dev); 99 if (ret) { 100 dev_dbg(dev, "unable to bound syscon device\n"); 101 return ret; 102 } 103 ret = device_probe(dev); 104 if (ret) { 105 dev_dbg(dev, "unable to probe syscon device\n"); 106 return ret; 107 } 108 109 *devp = dev; 110 return 0; 111} 112 113struct regmap *syscon_regmap_lookup_by_phandle(struct udevice *dev, 114 const char *name) 115{ 116 struct udevice *syscon; 117 struct regmap *r; 118 u32 phandle; 119 ofnode node; 120 int err; 121 122 err = uclass_get_device_by_phandle(UCLASS_SYSCON, dev, 123 name, &syscon); 124 if (err) { 125 /* found node with "syscon" compatible, not bounded to SYSCON */ 126 err = ofnode_read_u32(dev_ofnode(dev), name, &phandle); 127 if (err) 128 return ERR_PTR(err); 129 130 node = ofnode_get_by_phandle(phandle); 131 if (!ofnode_valid(node)) { 132 dev_dbg(dev, "unable to find syscon device\n"); 133 return ERR_PTR(-EINVAL); 134 } 135 err = syscon_probe_by_ofnode(node, &syscon); 136 if (err) 137 return ERR_PTR(-ENODEV); 138 } 139 140 r = syscon_get_regmap(syscon); 141 if (!r) { 142 dev_dbg(dev, "unable to find regmap\n"); 143 return ERR_PTR(-ENODEV); 144 } 145 146 return r; 147} 148 149int syscon_get_by_driver_data(ulong driver_data, struct udevice **devp) 150{ 151 int ret; 152 153 *devp = NULL; 154 155 ret = uclass_first_device_drvdata(UCLASS_SYSCON, driver_data, devp); 156 if (ret) 157 return ret; 158 159 return 0; 160} 161 162struct regmap *syscon_get_regmap_by_driver_data(ulong driver_data) 163{ 164 struct syscon_uc_info *priv; 165 struct udevice *dev; 166 int ret; 167 168 ret = syscon_get_by_driver_data(driver_data, &dev); 169 if (ret) 170 return ERR_PTR(ret); 171 priv = dev_get_uclass_priv(dev); 172 173 return priv->regmap; 174} 175 176void *syscon_get_first_range(ulong driver_data) 177{ 178 struct regmap *map; 179 180 map = syscon_get_regmap_by_driver_data(driver_data); 181 if (IS_ERR(map)) 182 return map; 183 return regmap_get_range(map, 0); 184} 185 186UCLASS_DRIVER(syscon) = { 187 .id = UCLASS_SYSCON, 188 .name = "syscon", 189 .per_device_auto = sizeof(struct syscon_uc_info), 190 .pre_probe = syscon_pre_probe, 191}; 192 193static const struct udevice_id generic_syscon_ids[] = { 194 { .compatible = "syscon" }, 195 { } 196}; 197 198U_BOOT_DRIVER(generic_syscon) = { 199 .name = "syscon", 200 .id = UCLASS_SYSCON, 201#if CONFIG_IS_ENABLED(OF_REAL) 202 .bind = dm_scan_fdt_dev, 203#endif 204 .of_match = generic_syscon_ids, 205}; 206 207/* 208 * Linux-compatible syscon-to-regmap 209 * The syscon node can be bound to another driver, but still works 210 * as a syscon provider. 211 */ 212struct regmap *syscon_node_to_regmap(ofnode node) 213{ 214 struct udevice *dev; 215 struct regmap *r; 216 217 if (uclass_get_device_by_ofnode(UCLASS_SYSCON, node, &dev)) 218 if (syscon_probe_by_ofnode(node, &dev)) 219 return ERR_PTR(-ENODEV); 220 221 r = syscon_get_regmap(dev); 222 if (!r) { 223 dev_dbg(dev, "unable to find regmap\n"); 224 return ERR_PTR(-ENODEV); 225 } 226 227 return r; 228} 229