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)) { | |
39 | pr_err("%s: device %s is not a SCOM controller\n", | |
40 | __func__, dev->full_name); | |
41 | return SCOM_MAP_INVALID; | |
42 | } | |
43 | gcid = of_get_property(dev, "ibm,chip-id", NULL); | |
44 | if (!gcid) { | |
45 | pr_err("%s: device %s has no ibm,chip-id\n", | |
46 | __func__, dev->full_name); | |
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 BH |
75 | { |
76 | /* | |
77 | * XSCOM indirect addresses have the top bit set. Additionally | |
e0cf9576 | 78 | * the rest of the top 3 nibbles is always 0. |
80546ac5 BH |
79 | * |
80 | * Because the debugfs interface uses signed offsets and shifts | |
81 | * the address left by 3, we basically cannot use the top 4 bits | |
82 | * of the 64-bit address, and thus cannot use the indirect bit. | |
83 | * | |
84 | * To deal with that, we support the indirect bit being in bit | |
85 | * 4 (IBM notation) instead of bit 0 in this API, we do the | |
86 | * conversion here. To leave room for further xscom address | |
87 | * expansion, we only clear out the top byte | |
88 | * | |
e0cf9576 BH |
89 | * For in-kernel use, we also support the real indirect bit, so |
90 | * we test for any of the top 5 bits | |
91 | * | |
80546ac5 | 92 | */ |
e0cf9576 BH |
93 | if (addr & (0x1full << 59)) |
94 | addr = (addr & ~(0xffull << 56)) | (1ull << 63); | |
95 | return addr; | |
80546ac5 BH |
96 | } |
97 | ||
d7a88c7e | 98 | static int opal_scom_read(scom_map_t map, u64 reg, u64 *value) |
8adae0c8 BH |
99 | { |
100 | struct opal_scom_map *m = map; | |
101 | int64_t rc; | |
01a9dbcc | 102 | __be64 v; |
8adae0c8 | 103 | |
e0cf9576 BH |
104 | reg = opal_scom_unmangle(m->addr + reg); |
105 | rc = opal_xscom_read(m->chip, reg, (__be64 *)__pa(&v)); | |
01a9dbcc | 106 | *value = be64_to_cpu(v); |
8adae0c8 BH |
107 | return opal_xscom_err_xlate(rc); |
108 | } | |
109 | ||
d7a88c7e | 110 | static int opal_scom_write(scom_map_t map, u64 reg, u64 value) |
8adae0c8 BH |
111 | { |
112 | struct opal_scom_map *m = map; | |
113 | int64_t rc; | |
114 | ||
e0cf9576 BH |
115 | reg = opal_scom_unmangle(m->addr + reg); |
116 | rc = opal_xscom_write(m->chip, reg, value); | |
8adae0c8 BH |
117 | return opal_xscom_err_xlate(rc); |
118 | } | |
119 | ||
120 | static const struct scom_controller opal_scom_controller = { | |
121 | .map = opal_scom_map, | |
122 | .unmap = opal_scom_unmap, | |
123 | .read = opal_scom_read, | |
124 | .write = opal_scom_write | |
125 | }; | |
126 | ||
127 | static int opal_xscom_init(void) | |
128 | { | |
129 | if (firmware_has_feature(FW_FEATURE_OPALv3)) | |
130 | scom_init(&opal_scom_controller); | |
131 | return 0; | |
132 | } | |
133 | arch_initcall(opal_xscom_init); |