1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * I/O delay strategies for inb_p/outb_p |
4 | * |
5 | * Allow for a DMI based override of port 0x80, needed for certain HP laptops |
6 | * and possibly other systems. Also allow for the gradual elimination of |
7 | * outb_p/inb_p API uses. |
8 | */ |
9 | #include <linux/kernel.h> |
10 | #include <linux/export.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/init.h> |
13 | #include <linux/dmi.h> |
14 | #include <linux/io.h> |
15 | |
16 | #define IO_DELAY_TYPE_0X80 0 |
17 | #define IO_DELAY_TYPE_0XED 1 |
18 | #define IO_DELAY_TYPE_UDELAY 2 |
19 | #define IO_DELAY_TYPE_NONE 3 |
20 | |
21 | #if defined(CONFIG_IO_DELAY_0X80) |
22 | #define DEFAULT_IO_DELAY_TYPE IO_DELAY_TYPE_0X80 |
23 | #elif defined(CONFIG_IO_DELAY_0XED) |
24 | #define DEFAULT_IO_DELAY_TYPE IO_DELAY_TYPE_0XED |
25 | #elif defined(CONFIG_IO_DELAY_UDELAY) |
26 | #define DEFAULT_IO_DELAY_TYPE IO_DELAY_TYPE_UDELAY |
27 | #elif defined(CONFIG_IO_DELAY_NONE) |
28 | #define DEFAULT_IO_DELAY_TYPE IO_DELAY_TYPE_NONE |
29 | #endif |
30 | |
31 | int io_delay_type __read_mostly = DEFAULT_IO_DELAY_TYPE; |
32 | |
33 | static int __initdata io_delay_override; |
34 | |
35 | /* |
36 | * Paravirt wants native_io_delay to be a constant. |
37 | */ |
38 | void native_io_delay(void) |
39 | { |
40 | switch (io_delay_type) { |
41 | default: |
42 | case IO_DELAY_TYPE_0X80: |
43 | asm volatile ("outb %al, $0x80" ); |
44 | break; |
45 | case IO_DELAY_TYPE_0XED: |
46 | asm volatile ("outb %al, $0xed" ); |
47 | break; |
48 | case IO_DELAY_TYPE_UDELAY: |
49 | /* |
50 | * 2 usecs is an upper-bound for the outb delay but |
51 | * note that udelay doesn't have the bus-level |
52 | * side-effects that outb does, nor does udelay() have |
53 | * precise timings during very early bootup (the delays |
54 | * are shorter until calibrated): |
55 | */ |
56 | udelay(2); |
57 | break; |
58 | case IO_DELAY_TYPE_NONE: |
59 | break; |
60 | } |
61 | } |
62 | EXPORT_SYMBOL(native_io_delay); |
63 | |
64 | static int __init dmi_io_delay_0xed_port(const struct dmi_system_id *id) |
65 | { |
66 | if (io_delay_type == IO_DELAY_TYPE_0X80) { |
67 | pr_notice("%s: using 0xed I/O delay port\n" , id->ident); |
68 | io_delay_type = IO_DELAY_TYPE_0XED; |
69 | } |
70 | |
71 | return 0; |
72 | } |
73 | |
74 | /* |
75 | * Quirk table for systems that misbehave (lock up, etc.) if port |
76 | * 0x80 is used: |
77 | */ |
78 | static const struct dmi_system_id io_delay_0xed_port_dmi_table[] __initconst = { |
79 | { |
80 | .callback = dmi_io_delay_0xed_port, |
81 | .ident = "Compaq Presario V6000" , |
82 | .matches = { |
83 | DMI_MATCH(DMI_BOARD_VENDOR, "Quanta" ), |
84 | DMI_MATCH(DMI_BOARD_NAME, "30B7" ) |
85 | } |
86 | }, |
87 | { |
88 | .callback = dmi_io_delay_0xed_port, |
89 | .ident = "HP Pavilion dv9000z" , |
90 | .matches = { |
91 | DMI_MATCH(DMI_BOARD_VENDOR, "Quanta" ), |
92 | DMI_MATCH(DMI_BOARD_NAME, "30B9" ) |
93 | } |
94 | }, |
95 | { |
96 | .callback = dmi_io_delay_0xed_port, |
97 | .ident = "HP Pavilion dv6000" , |
98 | .matches = { |
99 | DMI_MATCH(DMI_BOARD_VENDOR, "Quanta" ), |
100 | DMI_MATCH(DMI_BOARD_NAME, "30B8" ) |
101 | } |
102 | }, |
103 | { |
104 | .callback = dmi_io_delay_0xed_port, |
105 | .ident = "HP Pavilion tx1000" , |
106 | .matches = { |
107 | DMI_MATCH(DMI_BOARD_VENDOR, "Quanta" ), |
108 | DMI_MATCH(DMI_BOARD_NAME, "30BF" ) |
109 | } |
110 | }, |
111 | { |
112 | .callback = dmi_io_delay_0xed_port, |
113 | .ident = "Presario F700" , |
114 | .matches = { |
115 | DMI_MATCH(DMI_BOARD_VENDOR, "Quanta" ), |
116 | DMI_MATCH(DMI_BOARD_NAME, "30D3" ) |
117 | } |
118 | }, |
119 | { } |
120 | }; |
121 | |
122 | void __init io_delay_init(void) |
123 | { |
124 | if (!io_delay_override) |
125 | dmi_check_system(list: io_delay_0xed_port_dmi_table); |
126 | } |
127 | |
128 | static int __init io_delay_param(char *s) |
129 | { |
130 | if (!s) |
131 | return -EINVAL; |
132 | |
133 | if (!strcmp(s, "0x80" )) |
134 | io_delay_type = IO_DELAY_TYPE_0X80; |
135 | else if (!strcmp(s, "0xed" )) |
136 | io_delay_type = IO_DELAY_TYPE_0XED; |
137 | else if (!strcmp(s, "udelay" )) |
138 | io_delay_type = IO_DELAY_TYPE_UDELAY; |
139 | else if (!strcmp(s, "none" )) |
140 | io_delay_type = IO_DELAY_TYPE_NONE; |
141 | else |
142 | return -EINVAL; |
143 | |
144 | io_delay_override = 1; |
145 | return 0; |
146 | } |
147 | |
148 | early_param("io_delay" , io_delay_param); |
149 | |