1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Support for rfkill on some Fujitsu-Siemens Amilo laptops. |
4 | * Copyright 2011 Ben Hutchings. |
5 | * |
6 | * Based in part on the fsam7440 driver, which is: |
7 | * Copyright 2005 Alejandro Vidal Mata & Javier Vidal Mata. |
8 | * and on the fsaa1655g driver, which is: |
9 | * Copyright 2006 Martin Večeřa. |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/dmi.h> |
14 | #include <linux/i8042.h> |
15 | #include <linux/io.h> |
16 | #include <linux/moduleparam.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/rfkill.h> |
19 | |
20 | /* |
21 | * These values were obtained from disassembling and debugging the |
22 | * PM.exe program installed in the Fujitsu-Siemens AMILO A1655G |
23 | */ |
24 | #define A1655_WIFI_COMMAND 0x10C5 |
25 | #define A1655_WIFI_ON 0x25 |
26 | #define A1655_WIFI_OFF 0x45 |
27 | |
28 | static int amilo_a1655_rfkill_set_block(void *data, bool blocked) |
29 | { |
30 | u8 param = blocked ? A1655_WIFI_OFF : A1655_WIFI_ON; |
31 | int rc; |
32 | |
33 | i8042_lock_chip(); |
34 | rc = i8042_command(param: ¶m, A1655_WIFI_COMMAND); |
35 | i8042_unlock_chip(); |
36 | return rc; |
37 | } |
38 | |
39 | static const struct rfkill_ops amilo_a1655_rfkill_ops = { |
40 | .set_block = amilo_a1655_rfkill_set_block |
41 | }; |
42 | |
43 | /* |
44 | * These values were obtained from disassembling the PM.exe program |
45 | * installed in the Fujitsu-Siemens AMILO M 7440 |
46 | */ |
47 | #define M7440_PORT1 0x118f |
48 | #define M7440_PORT2 0x118e |
49 | #define M7440_RADIO_ON1 0x12 |
50 | #define M7440_RADIO_ON2 0x80 |
51 | #define M7440_RADIO_OFF1 0x10 |
52 | #define M7440_RADIO_OFF2 0x00 |
53 | |
54 | static int amilo_m7440_rfkill_set_block(void *data, bool blocked) |
55 | { |
56 | u8 val1 = blocked ? M7440_RADIO_OFF1 : M7440_RADIO_ON1; |
57 | u8 val2 = blocked ? M7440_RADIO_OFF2 : M7440_RADIO_ON2; |
58 | |
59 | outb(value: val1, M7440_PORT1); |
60 | outb(value: val2, M7440_PORT2); |
61 | |
62 | /* Check whether the state has changed correctly */ |
63 | if (inb(M7440_PORT1) != val1 || inb(M7440_PORT2) != val2) |
64 | return -EIO; |
65 | |
66 | return 0; |
67 | } |
68 | |
69 | static const struct rfkill_ops amilo_m7440_rfkill_ops = { |
70 | .set_block = amilo_m7440_rfkill_set_block |
71 | }; |
72 | |
73 | static const struct dmi_system_id amilo_rfkill_id_table[] = { |
74 | { |
75 | .matches = { |
76 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS" ), |
77 | DMI_MATCH(DMI_BOARD_NAME, "AMILO A1655" ), |
78 | }, |
79 | .driver_data = (void *)&amilo_a1655_rfkill_ops |
80 | }, |
81 | { |
82 | .matches = { |
83 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS" ), |
84 | DMI_MATCH(DMI_BOARD_NAME, "AMILO L1310" ), |
85 | }, |
86 | .driver_data = (void *)&amilo_a1655_rfkill_ops |
87 | }, |
88 | { |
89 | .matches = { |
90 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS" ), |
91 | DMI_MATCH(DMI_BOARD_NAME, "AMILO M7440" ), |
92 | }, |
93 | .driver_data = (void *)&amilo_m7440_rfkill_ops |
94 | }, |
95 | {} |
96 | }; |
97 | |
98 | static struct platform_device *amilo_rfkill_pdev; |
99 | static struct rfkill *amilo_rfkill_dev; |
100 | |
101 | static int amilo_rfkill_probe(struct platform_device *device) |
102 | { |
103 | int rc; |
104 | const struct dmi_system_id *system_id = |
105 | dmi_first_match(list: amilo_rfkill_id_table); |
106 | |
107 | if (!system_id) |
108 | return -ENXIO; |
109 | |
110 | amilo_rfkill_dev = rfkill_alloc(KBUILD_MODNAME, parent: &device->dev, |
111 | type: RFKILL_TYPE_WLAN, |
112 | ops: system_id->driver_data, NULL); |
113 | if (!amilo_rfkill_dev) |
114 | return -ENOMEM; |
115 | |
116 | rc = rfkill_register(rfkill: amilo_rfkill_dev); |
117 | if (rc) |
118 | goto fail; |
119 | |
120 | return 0; |
121 | |
122 | fail: |
123 | rfkill_destroy(rfkill: amilo_rfkill_dev); |
124 | return rc; |
125 | } |
126 | |
127 | static void amilo_rfkill_remove(struct platform_device *device) |
128 | { |
129 | rfkill_unregister(rfkill: amilo_rfkill_dev); |
130 | rfkill_destroy(rfkill: amilo_rfkill_dev); |
131 | } |
132 | |
133 | static struct platform_driver amilo_rfkill_driver = { |
134 | .driver = { |
135 | .name = KBUILD_MODNAME, |
136 | }, |
137 | .probe = amilo_rfkill_probe, |
138 | .remove_new = amilo_rfkill_remove, |
139 | }; |
140 | |
141 | static int __init amilo_rfkill_init(void) |
142 | { |
143 | int rc; |
144 | |
145 | if (dmi_first_match(list: amilo_rfkill_id_table) == NULL) |
146 | return -ENODEV; |
147 | |
148 | rc = platform_driver_register(&amilo_rfkill_driver); |
149 | if (rc) |
150 | return rc; |
151 | |
152 | amilo_rfkill_pdev = platform_device_register_simple(KBUILD_MODNAME, |
153 | PLATFORM_DEVID_NONE, |
154 | NULL, num: 0); |
155 | if (IS_ERR(ptr: amilo_rfkill_pdev)) { |
156 | rc = PTR_ERR(ptr: amilo_rfkill_pdev); |
157 | goto fail; |
158 | } |
159 | |
160 | return 0; |
161 | |
162 | fail: |
163 | platform_driver_unregister(&amilo_rfkill_driver); |
164 | return rc; |
165 | } |
166 | |
167 | static void __exit amilo_rfkill_exit(void) |
168 | { |
169 | platform_device_unregister(amilo_rfkill_pdev); |
170 | platform_driver_unregister(&amilo_rfkill_driver); |
171 | } |
172 | |
173 | MODULE_AUTHOR("Ben Hutchings <ben@decadent.org.uk>" ); |
174 | MODULE_LICENSE("GPL" ); |
175 | MODULE_DEVICE_TABLE(dmi, amilo_rfkill_id_table); |
176 | |
177 | module_init(amilo_rfkill_init); |
178 | module_exit(amilo_rfkill_exit); |
179 | |