]>
Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
35f0ce03 VM |
2 | /* |
3 | * IBM Real-Time Linux driver | |
4 | * | |
35f0ce03 VM |
5 | * Copyright (C) IBM Corporation, 2010 |
6 | * | |
7 | * Author: Keith Mannthey <kmannth@us.ibm.com> | |
8 | * Vernon Mauery <vernux@us.ibm.com> | |
35f0ce03 VM |
9 | */ |
10 | ||
323623a7 JP |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | ||
35f0ce03 VM |
13 | #include <linux/kernel.h> |
14 | #include <linux/delay.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/io.h> | |
35f0ce03 | 17 | #include <linux/dmi.h> |
1d37db77 | 18 | #include <linux/efi.h> |
35f0ce03 VM |
19 | #include <linux/mutex.h> |
20 | #include <asm/bios_ebda.h> | |
21 | ||
2f8e2c87 | 22 | #include <linux/io-64-nonatomic-lo-hi.h> |
797a796a | 23 | |
35f0ce03 VM |
24 | static bool force; |
25 | module_param(force, bool, 0); | |
26 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); | |
27 | ||
28 | static bool debug; | |
29 | module_param(debug, bool, 0644); | |
30 | MODULE_PARM_DESC(debug, "Show debug output"); | |
31 | ||
32 | MODULE_LICENSE("GPL"); | |
33 | MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>"); | |
34 | MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>"); | |
35 | ||
36 | #define RTL_ADDR_TYPE_IO 1 | |
37 | #define RTL_ADDR_TYPE_MMIO 2 | |
38 | ||
39 | #define RTL_CMD_ENTER_PRTM 1 | |
40 | #define RTL_CMD_EXIT_PRTM 2 | |
41 | ||
42 | /* The RTL table as presented by the EBDA: */ | |
43 | struct ibm_rtl_table { | |
44 | char signature[5]; /* signature should be "_RTL_" */ | |
45 | u8 version; | |
46 | u8 rt_status; | |
47 | u8 command; | |
48 | u8 command_status; | |
49 | u8 cmd_address_type; | |
50 | u8 cmd_granularity; | |
51 | u8 cmd_offset; | |
52 | u16 reserve1; | |
53 | u32 cmd_port_address; /* platform dependent address */ | |
54 | u32 cmd_port_value; /* platform dependent value */ | |
55 | } __attribute__((packed)); | |
56 | ||
57 | /* to locate "_RTL_" signature do a masked 5-byte integer compare */ | |
58 | #define RTL_SIGNATURE 0x0000005f4c54525fULL | |
59 | #define RTL_MASK 0x000000ffffffffffULL | |
60 | ||
323623a7 JP |
61 | #define RTL_DEBUG(fmt, ...) \ |
62 | do { \ | |
63 | if (debug) \ | |
64 | pr_info(fmt, ##__VA_ARGS__); \ | |
35f0ce03 VM |
65 | } while (0) |
66 | ||
67 | static DEFINE_MUTEX(rtl_lock); | |
68 | static struct ibm_rtl_table __iomem *rtl_table; | |
69 | static void __iomem *ebda_map; | |
70 | static void __iomem *rtl_cmd_addr; | |
71 | static u8 rtl_cmd_type; | |
72 | static u8 rtl_cmd_width; | |
73 | ||
74 | static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len) | |
75 | { | |
76 | if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO) | |
77 | return ioremap(addr, len); | |
78 | return ioport_map(addr, len); | |
79 | } | |
80 | ||
81 | static void rtl_port_unmap(void __iomem *addr) | |
82 | { | |
83 | if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO) | |
84 | iounmap(addr); | |
85 | else | |
86 | ioport_unmap(addr); | |
87 | } | |
88 | ||
89 | static int ibm_rtl_write(u8 value) | |
90 | { | |
91 | int ret = 0, count = 0; | |
cd0223c6 | 92 | u32 cmd_port_val; |
35f0ce03 | 93 | |
323623a7 | 94 | RTL_DEBUG("%s(%d)\n", __func__, value); |
35f0ce03 VM |
95 | |
96 | value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM; | |
97 | ||
98 | mutex_lock(&rtl_lock); | |
99 | ||
100 | if (ioread8(&rtl_table->rt_status) != value) { | |
101 | iowrite8(value, &rtl_table->command); | |
102 | ||
103 | switch (rtl_cmd_width) { | |
104 | case 8: | |
105 | cmd_port_val = ioread8(&rtl_table->cmd_port_value); | |
106 | RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); | |
107 | iowrite8((u8)cmd_port_val, rtl_cmd_addr); | |
108 | break; | |
109 | case 16: | |
110 | cmd_port_val = ioread16(&rtl_table->cmd_port_value); | |
111 | RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); | |
112 | iowrite16((u16)cmd_port_val, rtl_cmd_addr); | |
113 | break; | |
114 | case 32: | |
115 | cmd_port_val = ioread32(&rtl_table->cmd_port_value); | |
116 | RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val); | |
117 | iowrite32(cmd_port_val, rtl_cmd_addr); | |
118 | break; | |
119 | } | |
120 | ||
121 | while (ioread8(&rtl_table->command)) { | |
122 | msleep(10); | |
123 | if (count++ > 500) { | |
323623a7 JP |
124 | pr_err("Hardware not responding to " |
125 | "mode switch request\n"); | |
35f0ce03 VM |
126 | ret = -EIO; |
127 | break; | |
128 | } | |
129 | ||
130 | } | |
131 | ||
132 | if (ioread8(&rtl_table->command_status)) { | |
133 | RTL_DEBUG("command_status reports failed command\n"); | |
134 | ret = -EIO; | |
135 | } | |
136 | } | |
137 | ||
138 | mutex_unlock(&rtl_lock); | |
139 | return ret; | |
140 | } | |
141 | ||
15916a12 KS |
142 | static ssize_t rtl_show_version(struct device *dev, |
143 | struct device_attribute *attr, | |
35f0ce03 VM |
144 | char *buf) |
145 | { | |
146 | return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version)); | |
147 | } | |
148 | ||
15916a12 KS |
149 | static ssize_t rtl_show_state(struct device *dev, |
150 | struct device_attribute *attr, | |
35f0ce03 VM |
151 | char *buf) |
152 | { | |
153 | return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status)); | |
154 | } | |
155 | ||
15916a12 KS |
156 | static ssize_t rtl_set_state(struct device *dev, |
157 | struct device_attribute *attr, | |
35f0ce03 VM |
158 | const char *buf, |
159 | size_t count) | |
160 | { | |
161 | ssize_t ret; | |
162 | ||
163 | if (count < 1 || count > 2) | |
164 | return -EINVAL; | |
165 | ||
166 | switch (buf[0]) { | |
167 | case '0': | |
168 | ret = ibm_rtl_write(0); | |
169 | break; | |
170 | case '1': | |
171 | ret = ibm_rtl_write(1); | |
172 | break; | |
173 | default: | |
174 | ret = -EINVAL; | |
175 | } | |
176 | if (ret >= 0) | |
177 | ret = count; | |
178 | ||
179 | return ret; | |
180 | } | |
181 | ||
15916a12 | 182 | static struct bus_type rtl_subsys = { |
35f0ce03 | 183 | .name = "ibm_rtl", |
15916a12 | 184 | .dev_name = "ibm_rtl", |
35f0ce03 VM |
185 | }; |
186 | ||
15916a12 KS |
187 | static DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL); |
188 | static DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state); | |
35f0ce03 | 189 | |
15916a12 KS |
190 | static struct device_attribute *rtl_attributes[] = { |
191 | &dev_attr_version, | |
192 | &dev_attr_state, | |
35f0ce03 VM |
193 | NULL |
194 | }; | |
195 | ||
196 | ||
197 | static int rtl_setup_sysfs(void) { | |
198 | int ret, i; | |
35f0ce03 | 199 | |
15916a12 | 200 | ret = subsys_system_register(&rtl_subsys, NULL); |
35f0ce03 VM |
201 | if (!ret) { |
202 | for (i = 0; rtl_attributes[i]; i ++) | |
15916a12 | 203 | device_create_file(rtl_subsys.dev_root, rtl_attributes[i]); |
35f0ce03 VM |
204 | } |
205 | return ret; | |
206 | } | |
207 | ||
208 | static void rtl_teardown_sysfs(void) { | |
209 | int i; | |
210 | for (i = 0; rtl_attributes[i]; i ++) | |
15916a12 KS |
211 | device_remove_file(rtl_subsys.dev_root, rtl_attributes[i]); |
212 | bus_unregister(&rtl_subsys); | |
35f0ce03 VM |
213 | } |
214 | ||
35f0ce03 | 215 | |
6faadbbb | 216 | static const struct dmi_system_id ibm_rtl_dmi_table[] __initconst = { |
a2262260 VM |
217 | { \ |
218 | .matches = { \ | |
219 | DMI_MATCH(DMI_SYS_VENDOR, "IBM"), \ | |
220 | }, \ | |
221 | }, | |
35f0ce03 VM |
222 | { } |
223 | }; | |
224 | ||
225 | static int __init ibm_rtl_init(void) { | |
226 | unsigned long ebda_addr, ebda_size; | |
227 | unsigned int ebda_kb; | |
228 | int ret = -ENODEV, i; | |
229 | ||
230 | if (force) | |
323623a7 | 231 | pr_warn("module loaded by force\n"); |
35f0ce03 | 232 | /* first ensure that we are running on IBM HW */ |
83e68189 | 233 | else if (efi_enabled(EFI_BOOT) || !dmi_check_system(ibm_rtl_dmi_table)) |
35f0ce03 VM |
234 | return -ENODEV; |
235 | ||
236 | /* Get the address for the Extended BIOS Data Area */ | |
237 | ebda_addr = get_bios_ebda(); | |
238 | if (!ebda_addr) { | |
239 | RTL_DEBUG("no BIOS EBDA found\n"); | |
240 | return -ENODEV; | |
241 | } | |
242 | ||
243 | ebda_map = ioremap(ebda_addr, 4); | |
244 | if (!ebda_map) | |
245 | return -ENOMEM; | |
246 | ||
247 | /* First word in the EDBA is the Size in KB */ | |
248 | ebda_kb = ioread16(ebda_map); | |
249 | RTL_DEBUG("EBDA is %d kB\n", ebda_kb); | |
250 | ||
251 | if (ebda_kb == 0) | |
252 | goto out; | |
253 | ||
254 | iounmap(ebda_map); | |
255 | ebda_size = ebda_kb*1024; | |
256 | ||
257 | /* Remap the whole table */ | |
258 | ebda_map = ioremap(ebda_addr, ebda_size); | |
259 | if (!ebda_map) | |
260 | return -ENOMEM; | |
261 | ||
262 | /* search for the _RTL_ signature at the start of the table */ | |
263 | for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) { | |
264 | struct ibm_rtl_table __iomem * tmp; | |
265 | tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i); | |
266 | if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) { | |
267 | phys_addr_t addr; | |
268 | unsigned int plen; | |
3a35125f | 269 | RTL_DEBUG("found RTL_SIGNATURE at %p\n", tmp); |
35f0ce03 VM |
270 | rtl_table = tmp; |
271 | /* The address, value, width and offset are platform | |
272 | * dependent and found in the ibm_rtl_table */ | |
273 | rtl_cmd_width = ioread8(&rtl_table->cmd_granularity); | |
274 | rtl_cmd_type = ioread8(&rtl_table->cmd_address_type); | |
275 | RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n", | |
323623a7 | 276 | rtl_cmd_width, rtl_cmd_type); |
35f0ce03 | 277 | addr = ioread32(&rtl_table->cmd_port_address); |
c72b844e | 278 | RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr); |
35f0ce03 VM |
279 | plen = rtl_cmd_width/sizeof(char); |
280 | rtl_cmd_addr = rtl_port_map(addr, plen); | |
3a35125f | 281 | RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr); |
35f0ce03 VM |
282 | if (!rtl_cmd_addr) { |
283 | ret = -ENOMEM; | |
284 | break; | |
285 | } | |
286 | ret = rtl_setup_sysfs(); | |
287 | break; | |
288 | } | |
289 | } | |
290 | ||
291 | out: | |
292 | if (ret) { | |
293 | iounmap(ebda_map); | |
294 | rtl_port_unmap(rtl_cmd_addr); | |
295 | } | |
296 | ||
297 | return ret; | |
298 | } | |
299 | ||
300 | static void __exit ibm_rtl_exit(void) | |
301 | { | |
302 | if (rtl_table) { | |
303 | RTL_DEBUG("cleaning up"); | |
304 | /* do not leave the machine in SMI-free mode */ | |
305 | ibm_rtl_write(0); | |
306 | /* unmap, unlink and remove all traces */ | |
307 | rtl_teardown_sysfs(); | |
308 | iounmap(ebda_map); | |
309 | rtl_port_unmap(rtl_cmd_addr); | |
310 | } | |
311 | } | |
312 | ||
313 | module_init(ibm_rtl_init); | |
314 | module_exit(ibm_rtl_exit); |