Commit | Line | Data |
---|---|---|
8adae0c8 BH |
1 | /* |
2 | * PowerNV LPC bus handling. | |
3 | * | |
4 | * Copyright 2013 IBM Corp. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License | |
8 | * as published by the Free Software Foundation; either version | |
9 | * 2 of the License, or (at your option) any later version. | |
10 | */ | |
11 | ||
12 | #include <linux/kernel.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/bug.h> | |
15 | #include <linux/gfp.h> | |
16 | #include <linux/slab.h> | |
17 | ||
18 | #include <asm/machdep.h> | |
19 | #include <asm/firmware.h> | |
20 | #include <asm/opal.h> | |
21 | #include <asm/scom.h> | |
22 | ||
23 | /* | |
24 | * We could probably fit that inside the scom_map_t | |
25 | * which is a void* after all but it's really too ugly | |
26 | * so let's kmalloc it for now | |
27 | */ | |
28 | struct opal_scom_map { | |
29 | uint32_t chip; | |
d7a88c7e | 30 | uint64_t addr; |
8adae0c8 BH |
31 | }; |
32 | ||
33 | static scom_map_t opal_scom_map(struct device_node *dev, u64 reg, u64 count) | |
34 | { | |
35 | struct opal_scom_map *m; | |
36 | const __be32 *gcid; | |
37 | ||
38 | if (!of_get_property(dev, "scom-controller", NULL)) { | |
b7c670d6 RH |
39 | pr_err("%s: device %pOF is not a SCOM controller\n", |
40 | __func__, dev); | |
8adae0c8 BH |
41 | return SCOM_MAP_INVALID; |
42 | } | |
43 | gcid = of_get_property(dev, "ibm,chip-id", NULL); | |
44 | if (!gcid) { | |
b7c670d6 RH |
45 | pr_err("%s: device %pOF has no ibm,chip-id\n", |
46 | __func__, dev); | |
8adae0c8 BH |
47 | return SCOM_MAP_INVALID; |
48 | } | |
49 | m = kmalloc(sizeof(struct opal_scom_map), GFP_KERNEL); | |
50 | if (!m) | |
51 | return NULL; | |
52 | m->chip = be32_to_cpup(gcid); | |
53 | m->addr = reg; | |
54 | ||
55 | return (scom_map_t)m; | |
56 | } | |
57 | ||
58 | static void opal_scom_unmap(scom_map_t map) | |
59 | { | |
60 | kfree(map); | |
61 | } | |
62 | ||
63 | static int opal_xscom_err_xlate(int64_t rc) | |
64 | { | |
65 | switch(rc) { | |
66 | case 0: | |
67 | return 0; | |
68 | /* Add more translations if necessary */ | |
69 | default: | |
70 | return -EIO; | |
71 | } | |
72 | } | |
73 | ||
e0cf9576 | 74 | static u64 opal_scom_unmangle(u64 addr) |
80546ac5 | 75 | { |
517c2757 MN |
76 | u64 tmp; |
77 | ||
80546ac5 | 78 | /* |
517c2757 MN |
79 | * XSCOM addresses use the top nibble to set indirect mode and |
80 | * its form. Bits 4-11 are always 0. | |
80546ac5 BH |
81 | * |
82 | * Because the debugfs interface uses signed offsets and shifts | |
83 | * the address left by 3, we basically cannot use the top 4 bits | |
84 | * of the 64-bit address, and thus cannot use the indirect bit. | |
85 | * | |
517c2757 MN |
86 | * To deal with that, we support the indirect bits being in |
87 | * bits 4-7 (IBM notation) instead of bit 0-3 in this API, we | |
88 | * do the conversion here. | |
80546ac5 | 89 | * |
517c2757 MN |
90 | * For in-kernel use, we don't need to do this mangling. In |
91 | * kernel won't have bits 4-7 set. | |
e0cf9576 | 92 | * |
517c2757 MN |
93 | * So: |
94 | * debugfs will always set 0-3 = 0 and clear 4-7 | |
95 | * kernel will always clear 0-3 = 0 and set 4-7 | |
80546ac5 | 96 | */ |
517c2757 MN |
97 | tmp = addr; |
98 | tmp &= 0x0f00000000000000; | |
99 | addr &= 0xf0ffffffffffffff; | |
100 | addr |= tmp << 4; | |
101 | ||
e0cf9576 | 102 | return addr; |
80546ac5 BH |
103 | } |
104 | ||
d7a88c7e | 105 | static int opal_scom_read(scom_map_t map, u64 reg, u64 *value) |
8adae0c8 BH |
106 | { |
107 | struct opal_scom_map *m = map; | |
108 | int64_t rc; | |
01a9dbcc | 109 | __be64 v; |
8adae0c8 | 110 | |
e0cf9576 BH |
111 | reg = opal_scom_unmangle(m->addr + reg); |
112 | rc = opal_xscom_read(m->chip, reg, (__be64 *)__pa(&v)); | |
01a9dbcc | 113 | *value = be64_to_cpu(v); |
8adae0c8 BH |
114 | return opal_xscom_err_xlate(rc); |
115 | } | |
116 | ||
d7a88c7e | 117 | static int opal_scom_write(scom_map_t map, u64 reg, u64 value) |
8adae0c8 BH |
118 | { |
119 | struct opal_scom_map *m = map; | |
120 | int64_t rc; | |
121 | ||
e0cf9576 BH |
122 | reg = opal_scom_unmangle(m->addr + reg); |
123 | rc = opal_xscom_write(m->chip, reg, value); | |
8adae0c8 BH |
124 | return opal_xscom_err_xlate(rc); |
125 | } | |
126 | ||
127 | static const struct scom_controller opal_scom_controller = { | |
128 | .map = opal_scom_map, | |
129 | .unmap = opal_scom_unmap, | |
130 | .read = opal_scom_read, | |
131 | .write = opal_scom_write | |
132 | }; | |
133 | ||
134 | static int opal_xscom_init(void) | |
135 | { | |
e4d54f71 | 136 | if (firmware_has_feature(FW_FEATURE_OPAL)) |
8adae0c8 BH |
137 | scom_init(&opal_scom_controller); |
138 | return 0; | |
139 | } | |
b14726c5 | 140 | machine_arch_initcall(powernv, opal_xscom_init); |