Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

powerpc: Add SCOM infrastructure

SCOM is a side-band configuration bus implemented on some processors.
This code provides a way for code to map and operate on devices via
SCOM, while the details of how that is implemented is left up to a
SCOM "controller" in the platform code.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>

+357
+156
arch/powerpc/include/asm/scom.h
··· 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 + #ifndef _ASM_POWERPC_SCOM_H 22 + #define _ASM_POWERPC_SCOM_H 23 + 24 + #ifdef __KERNEL__ 25 + #ifndef __ASSEMBLY__ 26 + #ifdef CONFIG_PPC_SCOM 27 + 28 + /* 29 + * The SCOM bus is a sideband bus used for accessing various internal 30 + * registers of the processor or the chipset. The implementation details 31 + * differ between processors and platforms, and the access method as 32 + * well. 33 + * 34 + * This API allows to "map" ranges of SCOM register numbers associated 35 + * with a given SCOM controller. The later must be represented by a 36 + * device node, though some implementations might support NULL if there 37 + * is no possible ambiguity 38 + * 39 + * Then, scom_read/scom_write can be used to accesses registers inside 40 + * that range. The argument passed is a register number relative to 41 + * the beginning of the range mapped. 42 + */ 43 + 44 + typedef void *scom_map_t; 45 + 46 + /* Value for an invalid SCOM map */ 47 + #define SCOM_MAP_INVALID (NULL) 48 + 49 + /* The scom_controller data structure is what the platform passes 50 + * to the core code in scom_init, it provides the actual implementation 51 + * of all the SCOM functions 52 + */ 53 + struct scom_controller { 54 + scom_map_t (*map)(struct device_node *ctrl_dev, u64 reg, u64 count); 55 + void (*unmap)(scom_map_t map); 56 + 57 + u64 (*read)(scom_map_t map, u32 reg); 58 + void (*write)(scom_map_t map, u32 reg, u64 value); 59 + }; 60 + 61 + extern const struct scom_controller *scom_controller; 62 + 63 + /** 64 + * scom_init - Initialize the SCOM backend, called by the platform 65 + * @controller: The platform SCOM controller 66 + */ 67 + static inline void scom_init(const struct scom_controller *controller) 68 + { 69 + scom_controller = controller; 70 + } 71 + 72 + /** 73 + * scom_map_ok - Test is a SCOM mapping is successful 74 + * @map: The result of scom_map to test 75 + */ 76 + static inline int scom_map_ok(scom_map_t map) 77 + { 78 + return map != SCOM_MAP_INVALID; 79 + } 80 + 81 + /** 82 + * scom_map - Map a block of SCOM registers 83 + * @ctrl_dev: Device node of the SCOM controller 84 + * some implementations allow NULL here 85 + * @reg: first SCOM register to map 86 + * @count: Number of SCOM registers to map 87 + */ 88 + 89 + static inline scom_map_t scom_map(struct device_node *ctrl_dev, 90 + u64 reg, u64 count) 91 + { 92 + return scom_controller->map(ctrl_dev, reg, count); 93 + } 94 + 95 + /** 96 + * scom_find_parent - Find the SCOM controller for a device 97 + * @dev: OF node of the device 98 + * 99 + * This is not meant for general usage, but in combination with 100 + * scom_map() allows to map registers not represented by the 101 + * device own scom-reg property. Useful for applying HW workarounds 102 + * on things not properly represented in the device-tree for example. 103 + */ 104 + struct device_node *scom_find_parent(struct device_node *dev); 105 + 106 + 107 + /** 108 + * scom_map_device - Map a device's block of SCOM registers 109 + * @dev: OF node of the device 110 + * @index: Register bank index (index in "scom-reg" property) 111 + * 112 + * This function will use the device-tree binding for SCOM which 113 + * is to follow "scom-parent" properties until it finds a node with 114 + * a "scom-controller" property to find the controller. It will then 115 + * use the "scom-reg" property which is made of reg/count pairs, 116 + * each of them having a size defined by the controller's #scom-cells 117 + * property 118 + */ 119 + extern scom_map_t scom_map_device(struct device_node *dev, int index); 120 + 121 + 122 + /** 123 + * scom_unmap - Unmap a block of SCOM registers 124 + * @map: Result of scom_map is to be unmapped 125 + */ 126 + static inline void scom_unmap(scom_map_t map) 127 + { 128 + if (scom_map_ok(map)) 129 + scom_controller->unmap(map); 130 + } 131 + 132 + /** 133 + * scom_read - Read a SCOM register 134 + * @map: Result of scom_map 135 + * @reg: Register index within that map 136 + */ 137 + static inline u64 scom_read(scom_map_t map, u32 reg) 138 + { 139 + return scom_controller->read(map, reg); 140 + } 141 + 142 + /** 143 + * scom_write - Write to a SCOM register 144 + * @map: Result of scom_map 145 + * @reg: Register index within that map 146 + * @value: Value to write 147 + */ 148 + static inline void scom_write(scom_map_t map, u32 reg, u64 value) 149 + { 150 + scom_controller->write(map, reg, value); 151 + } 152 + 153 + #endif /* CONFIG_PPC_SCOM */ 154 + #endif /* __ASSEMBLY__ */ 155 + #endif /* __KERNEL__ */ 156 + #endif /* _ASM_POWERPC_SCOM_H */
+7
arch/powerpc/sysdev/Kconfig
··· 15 15 16 16 source "arch/powerpc/sysdev/xics/Kconfig" 17 17 18 + config PPC_SCOM 19 + bool 20 + 21 + config SCOM_DEBUGFS 22 + bool "Expose SCOM controllers via debugfs" 23 + depends on PPC_SCOM 24 + default n
+2
arch/powerpc/sysdev/Makefile
··· 58 58 obj-$(CONFIG_6xx) += 6xx-suspend.o 59 59 endif 60 60 61 + obj-$(CONFIG_PPC_SCOM) += scom.o 62 + 61 63 subdir-ccflags-$(CONFIG_PPC_WERROR) := -Werror 62 64 63 65 obj-$(CONFIG_PPC_XICS) += xics/
+192
arch/powerpc/sysdev/scom.c
··· 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 <asm/prom.h> 25 + #include <asm/scom.h> 26 + 27 + const struct scom_controller *scom_controller; 28 + EXPORT_SYMBOL_GPL(scom_controller); 29 + 30 + struct device_node *scom_find_parent(struct device_node *node) 31 + { 32 + struct device_node *par, *tmp; 33 + const u32 *p; 34 + 35 + for (par = of_node_get(node); par;) { 36 + if (of_get_property(par, "scom-controller", NULL)) 37 + break; 38 + p = of_get_property(par, "scom-parent", NULL); 39 + tmp = par; 40 + if (p == NULL) 41 + par = of_get_parent(par); 42 + else 43 + par = of_find_node_by_phandle(*p); 44 + of_node_put(tmp); 45 + } 46 + return par; 47 + } 48 + EXPORT_SYMBOL_GPL(scom_find_parent); 49 + 50 + scom_map_t scom_map_device(struct device_node *dev, int index) 51 + { 52 + struct device_node *parent; 53 + unsigned int cells, size; 54 + const u32 *prop; 55 + u64 reg, cnt; 56 + scom_map_t ret; 57 + 58 + parent = scom_find_parent(dev); 59 + 60 + if (parent == NULL) 61 + return 0; 62 + 63 + prop = of_get_property(parent, "#scom-cells", NULL); 64 + cells = prop ? *prop : 1; 65 + 66 + prop = of_get_property(dev, "scom-reg", &size); 67 + if (!prop) 68 + return 0; 69 + size >>= 2; 70 + 71 + if (index >= (size / (2*cells))) 72 + return 0; 73 + 74 + reg = of_read_number(&prop[index * cells * 2], cells); 75 + cnt = of_read_number(&prop[index * cells * 2 + cells], cells); 76 + 77 + ret = scom_map(parent, reg, cnt); 78 + of_node_put(parent); 79 + 80 + return ret; 81 + } 82 + EXPORT_SYMBOL_GPL(scom_map_device); 83 + 84 + #ifdef CONFIG_SCOM_DEBUGFS 85 + struct scom_debug_entry { 86 + struct device_node *dn; 87 + unsigned long addr; 88 + scom_map_t map; 89 + spinlock_t lock; 90 + char name[8]; 91 + struct debugfs_blob_wrapper blob; 92 + }; 93 + 94 + static int scom_addr_set(void *data, u64 val) 95 + { 96 + struct scom_debug_entry *ent = data; 97 + 98 + ent->addr = 0; 99 + scom_unmap(ent->map); 100 + 101 + ent->map = scom_map(ent->dn, val, 1); 102 + if (scom_map_ok(ent->map)) 103 + ent->addr = val; 104 + else 105 + return -EFAULT; 106 + 107 + return 0; 108 + } 109 + 110 + static int scom_addr_get(void *data, u64 *val) 111 + { 112 + struct scom_debug_entry *ent = data; 113 + *val = ent->addr; 114 + return 0; 115 + } 116 + DEFINE_SIMPLE_ATTRIBUTE(scom_addr_fops, scom_addr_get, scom_addr_set, 117 + "0x%llx\n"); 118 + 119 + static int scom_val_set(void *data, u64 val) 120 + { 121 + struct scom_debug_entry *ent = data; 122 + 123 + if (!scom_map_ok(ent->map)) 124 + return -EFAULT; 125 + 126 + scom_write(ent->map, 0, val); 127 + 128 + return 0; 129 + } 130 + 131 + static int scom_val_get(void *data, u64 *val) 132 + { 133 + struct scom_debug_entry *ent = data; 134 + 135 + if (!scom_map_ok(ent->map)) 136 + return -EFAULT; 137 + 138 + *val = scom_read(ent->map, 0); 139 + return 0; 140 + } 141 + DEFINE_SIMPLE_ATTRIBUTE(scom_val_fops, scom_val_get, scom_val_set, 142 + "0x%llx\n"); 143 + 144 + static int scom_debug_init_one(struct dentry *root, struct device_node *dn, 145 + int i) 146 + { 147 + struct scom_debug_entry *ent; 148 + struct dentry *dir; 149 + 150 + ent = kzalloc(sizeof(*ent), GFP_KERNEL); 151 + if (!ent) 152 + return -ENOMEM; 153 + 154 + ent->dn = of_node_get(dn); 155 + ent->map = SCOM_MAP_INVALID; 156 + spin_lock_init(&ent->lock); 157 + snprintf(ent->name, 8, "scom%d", i); 158 + ent->blob.data = dn->full_name; 159 + ent->blob.size = strlen(dn->full_name); 160 + 161 + dir = debugfs_create_dir(ent->name, root); 162 + if (!dir) { 163 + of_node_put(dn); 164 + kfree(ent); 165 + return -1; 166 + } 167 + 168 + debugfs_create_file("addr", 0600, dir, ent, &scom_addr_fops); 169 + debugfs_create_file("value", 0600, dir, ent, &scom_val_fops); 170 + debugfs_create_blob("path", 0400, dir, &ent->blob); 171 + 172 + return 0; 173 + } 174 + 175 + static int scom_debug_init(void) 176 + { 177 + struct device_node *dn; 178 + struct dentry *root; 179 + int i, rc; 180 + 181 + root = debugfs_create_dir("scom", powerpc_debugfs_root); 182 + if (!root) 183 + return -1; 184 + 185 + i = rc = 0; 186 + for_each_node_with_property(dn, "scom-controller") 187 + rc |= scom_debug_init_one(root, dn, i++); 188 + 189 + return rc; 190 + } 191 + device_initcall(scom_debug_init); 192 + #endif /* CONFIG_SCOM_DEBUGFS */