1/* 2 * Copyright 2010 Benjamin Herrenschmidt, IBM Corp 3 * <benh@kernel.crashing.org> 4 * and David Gibson, IBM Corporation. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 14 * the GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 21#include <linux/kernel.h> 22#include <linux/debugfs.h> 23#include <linux/slab.h> 24#include <linux/export.h> 25#include <asm/debug.h> 26#include <asm/prom.h> 27#include <asm/scom.h> 28#include <asm/uaccess.h> 29 30const struct scom_controller *scom_controller; 31EXPORT_SYMBOL_GPL(scom_controller); 32 33struct device_node *scom_find_parent(struct device_node *node) 34{ 35 struct device_node *par, *tmp; 36 const u32 *p; 37 38 for (par = of_node_get(node); par;) { 39 if (of_get_property(par, "scom-controller", NULL)) 40 break; 41 p = of_get_property(par, "scom-parent", NULL); 42 tmp = par; 43 if (p == NULL) 44 par = of_get_parent(par); 45 else 46 par = of_find_node_by_phandle(*p); 47 of_node_put(tmp); 48 } 49 return par; 50} 51EXPORT_SYMBOL_GPL(scom_find_parent); 52 53scom_map_t scom_map_device(struct device_node *dev, int index) 54{ 55 struct device_node *parent; 56 unsigned int cells, size; 57 const __be32 *prop, *sprop; 58 u64 reg, cnt; 59 scom_map_t ret; 60 61 parent = scom_find_parent(dev); 62 63 if (parent == NULL) 64 return 0; 65 66 /* 67 * We support "scom-reg" properties for adding scom registers 68 * to a random device-tree node with an explicit scom-parent 69 * 70 * We also support the simple "reg" property if the device is 71 * a direct child of a scom controller. 72 * 73 * In case both exist, "scom-reg" takes precedence. 74 */ 75 prop = of_get_property(dev, "scom-reg", &size); 76 sprop = of_get_property(parent, "#scom-cells", NULL); 77 if (!prop && parent == dev->parent) { 78 prop = of_get_property(dev, "reg", &size); 79 sprop = of_get_property(parent, "#address-cells", NULL); 80 } 81 if (!prop) 82 return NULL; 83 cells = sprop ? be32_to_cpup(sprop) : 1; 84 size >>= 2; 85 86 if (index >= (size / (2*cells))) 87 return 0; 88 89 reg = of_read_number(&prop[index * cells * 2], cells); 90 cnt = of_read_number(&prop[index * cells * 2 + cells], cells); 91 92 ret = scom_map(parent, reg, cnt); 93 of_node_put(parent); 94 95 return ret; 96} 97EXPORT_SYMBOL_GPL(scom_map_device); 98 99#ifdef CONFIG_SCOM_DEBUGFS 100struct scom_debug_entry { 101 struct device_node *dn; 102 struct debugfs_blob_wrapper path; 103 char name[16]; 104}; 105 106static ssize_t scom_debug_read(struct file *filp, char __user *ubuf, 107 size_t count, loff_t *ppos) 108{ 109 struct scom_debug_entry *ent = filp->private_data; 110 u64 __user *ubuf64 = (u64 __user *)ubuf; 111 loff_t off = *ppos; 112 ssize_t done = 0; 113 u64 reg, reg_cnt, val; 114 scom_map_t map; 115 int rc; 116 117 if (off < 0 || (off & 7) || (count & 7)) 118 return -EINVAL; 119 reg = off >> 3; 120 reg_cnt = count >> 3; 121 122 map = scom_map(ent->dn, reg, reg_cnt); 123 if (!scom_map_ok(map)) 124 return -ENXIO; 125 126 for (reg = 0; reg < reg_cnt; reg++) { 127 rc = scom_read(map, reg, &val); 128 if (!rc) 129 rc = put_user(val, ubuf64); 130 if (rc) { 131 if (!done) 132 done = rc; 133 break; 134 } 135 ubuf64++; 136 *ppos += 8; 137 done += 8; 138 } 139 scom_unmap(map); 140 return done; 141} 142 143static ssize_t scom_debug_write(struct file* filp, const char __user *ubuf, 144 size_t count, loff_t *ppos) 145{ 146 struct scom_debug_entry *ent = filp->private_data; 147 u64 __user *ubuf64 = (u64 __user *)ubuf; 148 loff_t off = *ppos; 149 ssize_t done = 0; 150 u64 reg, reg_cnt, val; 151 scom_map_t map; 152 int rc; 153 154 if (off < 0 || (off & 7) || (count & 7)) 155 return -EINVAL; 156 reg = off >> 3; 157 reg_cnt = count >> 3; 158 159 map = scom_map(ent->dn, reg, reg_cnt); 160 if (!scom_map_ok(map)) 161 return -ENXIO; 162 163 for (reg = 0; reg < reg_cnt; reg++) { 164 rc = get_user(val, ubuf64); 165 if (!rc) 166 rc = scom_write(map, reg, val); 167 if (rc) { 168 if (!done) 169 done = rc; 170 break; 171 } 172 ubuf64++; 173 done += 8; 174 } 175 scom_unmap(map); 176 return done; 177} 178 179static const struct file_operations scom_debug_fops = { 180 .read = scom_debug_read, 181 .write = scom_debug_write, 182 .open = simple_open, 183 .llseek = default_llseek, 184}; 185 186static int scom_debug_init_one(struct dentry *root, struct device_node *dn, 187 int i) 188{ 189 struct scom_debug_entry *ent; 190 struct dentry *dir; 191 192 ent = kzalloc(sizeof(*ent), GFP_KERNEL); 193 if (!ent) 194 return -ENOMEM; 195 196 ent->dn = of_node_get(dn); 197 snprintf(ent->name, 16, "%08x", i); 198 ent->path.data = (void*) dn->full_name; 199 ent->path.size = strlen(dn->full_name); 200 201 dir = debugfs_create_dir(ent->name, root); 202 if (!dir) { 203 of_node_put(dn); 204 kfree(ent); 205 return -1; 206 } 207 208 debugfs_create_blob("devspec", 0400, dir, &ent->path); 209 debugfs_create_file("access", 0600, dir, ent, &scom_debug_fops); 210 211 return 0; 212} 213 214static int scom_debug_init(void) 215{ 216 struct device_node *dn; 217 struct dentry *root; 218 int i, rc; 219 220 root = debugfs_create_dir("scom", powerpc_debugfs_root); 221 if (!root) 222 return -1; 223 224 i = rc = 0; 225 for_each_node_with_property(dn, "scom-controller") { 226 int id = of_get_ibm_chip_id(dn); 227 if (id == -1) 228 id = i; 229 rc |= scom_debug_init_one(root, dn, id); 230 i++; 231 } 232 233 return rc; 234} 235device_initcall(scom_debug_init); 236#endif /* CONFIG_SCOM_DEBUGFS */ 237