]>
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 | ||
80546ac5 BH |
74 | static u64 opal_scom_unmangle(u64 reg) |
75 | { | |
76 | /* | |
77 | * XSCOM indirect addresses have the top bit set. Additionally | |
78 | * the reset of the top 3 nibbles is always 0. | |
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 | * | |
89 | */ | |
90 | if (reg & (1ull << 59)) | |
91 | reg = (reg & ~(0xffull << 56)) | (1ull << 63); | |
92 | return reg; | |
93 | } | |
94 | ||
d7a88c7e | 95 | static int opal_scom_read(scom_map_t map, u64 reg, u64 *value) |
8adae0c8 BH |
96 | { |
97 | struct opal_scom_map *m = map; | |
98 | int64_t rc; | |
01a9dbcc | 99 | __be64 v; |
8adae0c8 | 100 | |
80546ac5 | 101 | reg = opal_scom_unmangle(reg); |
01a9dbcc AB |
102 | rc = opal_xscom_read(m->chip, m->addr + reg, (__be64 *)__pa(&v)); |
103 | *value = be64_to_cpu(v); | |
8adae0c8 BH |
104 | return opal_xscom_err_xlate(rc); |
105 | } | |
106 | ||
d7a88c7e | 107 | static int opal_scom_write(scom_map_t map, u64 reg, u64 value) |
8adae0c8 BH |
108 | { |
109 | struct opal_scom_map *m = map; | |
110 | int64_t rc; | |
111 | ||
80546ac5 | 112 | reg = opal_scom_unmangle(reg); |
8adae0c8 BH |
113 | rc = opal_xscom_write(m->chip, m->addr + reg, value); |
114 | return opal_xscom_err_xlate(rc); | |
115 | } | |
116 | ||
117 | static const struct scom_controller opal_scom_controller = { | |
118 | .map = opal_scom_map, | |
119 | .unmap = opal_scom_unmap, | |
120 | .read = opal_scom_read, | |
121 | .write = opal_scom_write | |
122 | }; | |
123 | ||
124 | static int opal_xscom_init(void) | |
125 | { | |
126 | if (firmware_has_feature(FW_FEATURE_OPALv3)) | |
127 | scom_init(&opal_scom_controller); | |
128 | return 0; | |
129 | } | |
130 | arch_initcall(opal_xscom_init); |