0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0013
0014 #include <linux/kernel.h>
0015 #include <linux/module.h>
0016 #include <linux/init.h>
0017 #include <linux/slab.h>
0018 #include <linux/acpi.h>
0019 #include <linux/dmi.h>
0020 #include <linux/input.h>
0021 #include <linux/input/sparse-keymap.h>
0022 #include <linux/leds.h>
0023 #include <linux/platform_device.h>
0024
0025 #define TOPSTAR_LAPTOP_CLASS "topstar"
0026
0027 struct topstar_laptop {
0028 struct acpi_device *device;
0029 struct platform_device *platform;
0030 struct input_dev *input;
0031 struct led_classdev led;
0032 };
0033
0034
0035
0036
0037
0038 static enum led_brightness topstar_led_get(struct led_classdev *led)
0039 {
0040 return led->brightness;
0041 }
0042
0043 static int topstar_led_set(struct led_classdev *led,
0044 enum led_brightness state)
0045 {
0046 struct topstar_laptop *topstar = container_of(led,
0047 struct topstar_laptop, led);
0048
0049 struct acpi_object_list params;
0050 union acpi_object in_obj;
0051 unsigned long long int ret;
0052 acpi_status status;
0053
0054 params.count = 1;
0055 params.pointer = &in_obj;
0056 in_obj.type = ACPI_TYPE_INTEGER;
0057 in_obj.integer.value = 0x83;
0058
0059
0060
0061
0062
0063 status = acpi_evaluate_integer(topstar->device->handle,
0064 "GETX", ¶ms, &ret);
0065 if (ACPI_FAILURE(status))
0066 return -1;
0067
0068
0069
0070
0071
0072
0073
0074 if ((ret == 0x30001 && state == LED_OFF)
0075 || (ret == 0x30000 && state != LED_OFF)) {
0076 status = acpi_execute_simple_method(topstar->device->handle,
0077 "FNCX", 0x83);
0078 if (ACPI_FAILURE(status))
0079 return -1;
0080 }
0081
0082 return 0;
0083 }
0084
0085 static int topstar_led_init(struct topstar_laptop *topstar)
0086 {
0087 topstar->led = (struct led_classdev) {
0088 .default_trigger = "rfkill0",
0089 .brightness_get = topstar_led_get,
0090 .brightness_set_blocking = topstar_led_set,
0091 .name = TOPSTAR_LAPTOP_CLASS "::wlan",
0092 };
0093
0094 return led_classdev_register(&topstar->platform->dev, &topstar->led);
0095 }
0096
0097 static void topstar_led_exit(struct topstar_laptop *topstar)
0098 {
0099 led_classdev_unregister(&topstar->led);
0100 }
0101
0102
0103
0104
0105
0106 static const struct key_entry topstar_keymap[] = {
0107 { KE_KEY, 0x80, { KEY_BRIGHTNESSUP } },
0108 { KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } },
0109 { KE_KEY, 0x83, { KEY_VOLUMEUP } },
0110 { KE_KEY, 0x84, { KEY_VOLUMEDOWN } },
0111 { KE_KEY, 0x85, { KEY_MUTE } },
0112 { KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } },
0113 { KE_KEY, 0x87, { KEY_F13 } },
0114 { KE_KEY, 0x88, { KEY_WLAN } },
0115 { KE_KEY, 0x8a, { KEY_WWW } },
0116 { KE_KEY, 0x8b, { KEY_MAIL } },
0117 { KE_KEY, 0x8c, { KEY_MEDIA } },
0118
0119
0120 { KE_IGNORE, 0x82, },
0121 { KE_IGNORE, 0x8e, },
0122 { KE_IGNORE, 0x8f, },
0123 { KE_IGNORE, 0x90, },
0124
0125
0126
0127
0128
0129
0130 { KE_KEY, 0x96, { KEY_F14 } },
0131 { KE_KEY, 0x97, { KEY_F14 } },
0132
0133 { KE_END, 0 }
0134 };
0135
0136 static void topstar_input_notify(struct topstar_laptop *topstar, int event)
0137 {
0138 if (!sparse_keymap_report_event(topstar->input, event, 1, true))
0139 pr_info("unknown event = 0x%02x\n", event);
0140 }
0141
0142 static int topstar_input_init(struct topstar_laptop *topstar)
0143 {
0144 struct input_dev *input;
0145 int err;
0146
0147 input = input_allocate_device();
0148 if (!input)
0149 return -ENOMEM;
0150
0151 input->name = "Topstar Laptop extra buttons";
0152 input->phys = TOPSTAR_LAPTOP_CLASS "/input0";
0153 input->id.bustype = BUS_HOST;
0154 input->dev.parent = &topstar->platform->dev;
0155
0156 err = sparse_keymap_setup(input, topstar_keymap, NULL);
0157 if (err) {
0158 pr_err("Unable to setup input device keymap\n");
0159 goto err_free_dev;
0160 }
0161
0162 err = input_register_device(input);
0163 if (err) {
0164 pr_err("Unable to register input device\n");
0165 goto err_free_dev;
0166 }
0167
0168 topstar->input = input;
0169 return 0;
0170
0171 err_free_dev:
0172 input_free_device(input);
0173 return err;
0174 }
0175
0176 static void topstar_input_exit(struct topstar_laptop *topstar)
0177 {
0178 input_unregister_device(topstar->input);
0179 }
0180
0181
0182
0183
0184
0185 static struct platform_driver topstar_platform_driver = {
0186 .driver = {
0187 .name = TOPSTAR_LAPTOP_CLASS,
0188 },
0189 };
0190
0191 static int topstar_platform_init(struct topstar_laptop *topstar)
0192 {
0193 int err;
0194
0195 topstar->platform = platform_device_alloc(TOPSTAR_LAPTOP_CLASS, -1);
0196 if (!topstar->platform)
0197 return -ENOMEM;
0198
0199 platform_set_drvdata(topstar->platform, topstar);
0200
0201 err = platform_device_add(topstar->platform);
0202 if (err)
0203 goto err_device_put;
0204
0205 return 0;
0206
0207 err_device_put:
0208 platform_device_put(topstar->platform);
0209 return err;
0210 }
0211
0212 static void topstar_platform_exit(struct topstar_laptop *topstar)
0213 {
0214 platform_device_unregister(topstar->platform);
0215 }
0216
0217
0218
0219
0220
0221 static int topstar_acpi_fncx_switch(struct acpi_device *device, bool state)
0222 {
0223 acpi_status status;
0224 u64 arg = state ? 0x86 : 0x87;
0225
0226 status = acpi_execute_simple_method(device->handle, "FNCX", arg);
0227 if (ACPI_FAILURE(status)) {
0228 pr_err("Unable to switch FNCX notifications\n");
0229 return -ENODEV;
0230 }
0231
0232 return 0;
0233 }
0234
0235 static void topstar_acpi_notify(struct acpi_device *device, u32 event)
0236 {
0237 struct topstar_laptop *topstar = acpi_driver_data(device);
0238 static bool dup_evnt[2];
0239 bool *dup;
0240
0241
0242 if (event == 0x83 || event == 0x84) {
0243 dup = &dup_evnt[event - 0x83];
0244 if (*dup) {
0245 *dup = false;
0246 return;
0247 }
0248 *dup = true;
0249 }
0250
0251 topstar_input_notify(topstar, event);
0252 }
0253
0254 static int topstar_acpi_init(struct topstar_laptop *topstar)
0255 {
0256 return topstar_acpi_fncx_switch(topstar->device, true);
0257 }
0258
0259 static void topstar_acpi_exit(struct topstar_laptop *topstar)
0260 {
0261 topstar_acpi_fncx_switch(topstar->device, false);
0262 }
0263
0264
0265
0266
0267
0268 static bool led_workaround;
0269
0270 static int dmi_led_workaround(const struct dmi_system_id *id)
0271 {
0272 led_workaround = true;
0273 return 0;
0274 }
0275
0276 static const struct dmi_system_id topstar_dmi_ids[] = {
0277 {
0278 .callback = dmi_led_workaround,
0279 .ident = "Topstar U931/RVP7",
0280 .matches = {
0281 DMI_MATCH(DMI_BOARD_NAME, "U931"),
0282 DMI_MATCH(DMI_BOARD_VERSION, "RVP7"),
0283 },
0284 },
0285 {}
0286 };
0287
0288 static int topstar_acpi_add(struct acpi_device *device)
0289 {
0290 struct topstar_laptop *topstar;
0291 int err;
0292
0293 dmi_check_system(topstar_dmi_ids);
0294
0295 topstar = kzalloc(sizeof(struct topstar_laptop), GFP_KERNEL);
0296 if (!topstar)
0297 return -ENOMEM;
0298
0299 strcpy(acpi_device_name(device), "Topstar TPSACPI");
0300 strcpy(acpi_device_class(device), TOPSTAR_LAPTOP_CLASS);
0301 device->driver_data = topstar;
0302 topstar->device = device;
0303
0304 err = topstar_acpi_init(topstar);
0305 if (err)
0306 goto err_free;
0307
0308 err = topstar_platform_init(topstar);
0309 if (err)
0310 goto err_acpi_exit;
0311
0312 err = topstar_input_init(topstar);
0313 if (err)
0314 goto err_platform_exit;
0315
0316 if (led_workaround) {
0317 err = topstar_led_init(topstar);
0318 if (err)
0319 goto err_input_exit;
0320 }
0321
0322 return 0;
0323
0324 err_input_exit:
0325 topstar_input_exit(topstar);
0326 err_platform_exit:
0327 topstar_platform_exit(topstar);
0328 err_acpi_exit:
0329 topstar_acpi_exit(topstar);
0330 err_free:
0331 kfree(topstar);
0332 return err;
0333 }
0334
0335 static int topstar_acpi_remove(struct acpi_device *device)
0336 {
0337 struct topstar_laptop *topstar = acpi_driver_data(device);
0338
0339 if (led_workaround)
0340 topstar_led_exit(topstar);
0341
0342 topstar_input_exit(topstar);
0343 topstar_platform_exit(topstar);
0344 topstar_acpi_exit(topstar);
0345
0346 kfree(topstar);
0347 return 0;
0348 }
0349
0350 static const struct acpi_device_id topstar_device_ids[] = {
0351 { "TPS0001", 0 },
0352 { "TPSACPI01", 0 },
0353 { "", 0 },
0354 };
0355 MODULE_DEVICE_TABLE(acpi, topstar_device_ids);
0356
0357 static struct acpi_driver topstar_acpi_driver = {
0358 .name = "Topstar laptop ACPI driver",
0359 .class = TOPSTAR_LAPTOP_CLASS,
0360 .ids = topstar_device_ids,
0361 .ops = {
0362 .add = topstar_acpi_add,
0363 .remove = topstar_acpi_remove,
0364 .notify = topstar_acpi_notify,
0365 },
0366 };
0367
0368 static int __init topstar_laptop_init(void)
0369 {
0370 int ret;
0371
0372 ret = platform_driver_register(&topstar_platform_driver);
0373 if (ret < 0)
0374 return ret;
0375
0376 ret = acpi_bus_register_driver(&topstar_acpi_driver);
0377 if (ret < 0)
0378 goto err_driver_unreg;
0379
0380 pr_info("ACPI extras driver loaded\n");
0381 return 0;
0382
0383 err_driver_unreg:
0384 platform_driver_unregister(&topstar_platform_driver);
0385 return ret;
0386 }
0387
0388 static void __exit topstar_laptop_exit(void)
0389 {
0390 acpi_bus_unregister_driver(&topstar_acpi_driver);
0391 platform_driver_unregister(&topstar_platform_driver);
0392 }
0393
0394 module_init(topstar_laptop_init);
0395 module_exit(topstar_laptop_exit);
0396
0397 MODULE_AUTHOR("Herton Ronaldo Krzesinski");
0398 MODULE_AUTHOR("Guillaume Douézan-Grard");
0399 MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver");
0400 MODULE_LICENSE("GPL");