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