1c8f7a62cSLen Brown /* 2c8f7a62cSLen Brown * dock.c - ACPI dock station driver 3c8f7a62cSLen Brown * 48cc25681SRafael J. Wysocki * Copyright (C) 2006, 2014, Intel Corp. 58cc25681SRafael J. Wysocki * Author: Kristen Carlson Accardi <kristen.c.accardi@intel.com> 68cc25681SRafael J. Wysocki * Rafael J. Wysocki <rafael.j.wysocki@intel.com> 7c8f7a62cSLen Brown * 8c8f7a62cSLen Brown * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9c8f7a62cSLen Brown * 10c8f7a62cSLen Brown * This program is free software; you can redistribute it and/or modify 11c8f7a62cSLen Brown * it under the terms of the GNU General Public License as published by 12c8f7a62cSLen Brown * the Free Software Foundation; either version 2 of the License, or (at 13c8f7a62cSLen Brown * your option) any later version. 14c8f7a62cSLen Brown * 15c8f7a62cSLen Brown * This program is distributed in the hope that it will be useful, but 16c8f7a62cSLen Brown * WITHOUT ANY WARRANTY; without even the implied warranty of 17c8f7a62cSLen Brown * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18c8f7a62cSLen Brown * General Public License for more details. 19c8f7a62cSLen Brown * 20c8f7a62cSLen Brown * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 21c8f7a62cSLen Brown */ 22c8f7a62cSLen Brown 23c8f7a62cSLen Brown #include <linux/kernel.h> 240f093e95SPaul Gortmaker #include <linux/moduleparam.h> 255a0e3ad6STejun Heo #include <linux/slab.h> 26c8f7a62cSLen Brown #include <linux/init.h> 27c8f7a62cSLen Brown #include <linux/types.h> 28c8f7a62cSLen Brown #include <linux/notifier.h> 29671adbecSKristen Carlson Accardi #include <linux/platform_device.h> 30914e2637SAl Viro #include <linux/jiffies.h> 3162a6d7fdSRandy Dunlap #include <linux/stddef.h> 32cd73018fSToshi Kani #include <linux/acpi.h> 33c8f7a62cSLen Brown 343f9eed5cSRashika #include "internal.h" 353f9eed5cSRashika 36f52fd66dSLen Brown ACPI_MODULE_NAME("dock"); 37c8f7a62cSLen Brown 3890ab5ee9SRusty Russell static bool immediate_undock = 1; 39a0cd35fdSKristen Carlson Accardi module_param(immediate_undock, bool, 0644); 40a0cd35fdSKristen Carlson Accardi MODULE_PARM_DESC(immediate_undock, "1 (default) will cause the driver to " 41a0cd35fdSKristen Carlson Accardi "undock immediately when the undock button is pressed, 0 will cause" 42a0cd35fdSKristen Carlson Accardi " the driver to wait for userspace to write the undock sysfs file " 43a0cd35fdSKristen Carlson Accardi " before undocking"); 44a0cd35fdSKristen Carlson Accardi 45c8f7a62cSLen Brown struct dock_station { 46c8f7a62cSLen Brown acpi_handle handle; 47c8f7a62cSLen Brown unsigned long last_dock_time; 48c8f7a62cSLen Brown u32 flags; 49c8f7a62cSLen Brown struct list_head dependent_devices; 50db350b08SShaohua Li 5150d716e4SAlex Chiang struct list_head sibling; 52db350b08SShaohua Li struct platform_device *dock_device; 53c8f7a62cSLen Brown }; 54db350b08SShaohua Li static LIST_HEAD(dock_stations); 55db350b08SShaohua Li static int dock_station_count; 56c8f7a62cSLen Brown 57c8f7a62cSLen Brown struct dock_dependent_device { 58c8f7a62cSLen Brown struct list_head list; 593b52b21fSRafael J. Wysocki struct acpi_device *adev; 60c8f7a62cSLen Brown }; 61c8f7a62cSLen Brown 62c8f7a62cSLen Brown #define DOCK_DOCKING 0x00000001 63a0cd35fdSKristen Carlson Accardi #define DOCK_UNDOCKING 0x00000002 64db350b08SShaohua Li #define DOCK_IS_DOCK 0x00000010 65db350b08SShaohua Li #define DOCK_IS_ATA 0x00000020 66db350b08SShaohua Li #define DOCK_IS_BAT 0x00000040 675669021eSKristen Carlson Accardi #define DOCK_EVENT 3 685669021eSKristen Carlson Accardi #define UNDOCK_EVENT 2 69c8f7a62cSLen Brown 70f09ce741SRafael J. Wysocki enum dock_callback_type { 71f09ce741SRafael J. Wysocki DOCK_CALL_HANDLER, 72f09ce741SRafael J. Wysocki DOCK_CALL_FIXUP, 73f09ce741SRafael J. Wysocki DOCK_CALL_UEVENT, 74f09ce741SRafael J. Wysocki }; 75f09ce741SRafael J. Wysocki 76c8f7a62cSLen Brown /***************************************************************************** 77c8f7a62cSLen Brown * Dock Dependent device functions * 78c8f7a62cSLen Brown *****************************************************************************/ 79c8f7a62cSLen Brown /** 80f69cfdd2SAlex Chiang * add_dock_dependent_device - associate a device with the dock station 813b52b21fSRafael J. Wysocki * @ds: Dock station. 823b52b21fSRafael J. Wysocki * @adev: Dependent ACPI device object. 83c8f7a62cSLen Brown * 84f69cfdd2SAlex Chiang * Add the dependent device to the dock's dependent device list. 85c8f7a62cSLen Brown */ 863b52b21fSRafael J. Wysocki static int add_dock_dependent_device(struct dock_station *ds, 873b52b21fSRafael J. Wysocki struct acpi_device *adev) 88c8f7a62cSLen Brown { 89c8f7a62cSLen Brown struct dock_dependent_device *dd; 90c8f7a62cSLen Brown 91c8f7a62cSLen Brown dd = kzalloc(sizeof(*dd), GFP_KERNEL); 92f69cfdd2SAlex Chiang if (!dd) 93f69cfdd2SAlex Chiang return -ENOMEM; 94f69cfdd2SAlex Chiang 953b52b21fSRafael J. Wysocki dd->adev = adev; 96c8f7a62cSLen Brown INIT_LIST_HEAD(&dd->list); 97c8f7a62cSLen Brown list_add_tail(&dd->list, &ds->dependent_devices); 98f69cfdd2SAlex Chiang 99f69cfdd2SAlex Chiang return 0; 100c8f7a62cSLen Brown } 101c8f7a62cSLen Brown 10221a31013SRafael J. Wysocki static void dock_hotplug_event(struct dock_dependent_device *dd, u32 event, 103f09ce741SRafael J. Wysocki enum dock_callback_type cb_type) 10421a31013SRafael J. Wysocki { 105edf5bf34SRafael J. Wysocki struct acpi_device *adev = dd->adev; 10621a31013SRafael J. Wysocki 107edf5bf34SRafael J. Wysocki acpi_lock_hp_context(); 108edf5bf34SRafael J. Wysocki 109edf5bf34SRafael J. Wysocki if (!adev->hp) 1102f16817dSRafael J. Wysocki goto out; 111edf5bf34SRafael J. Wysocki 112edf5bf34SRafael J. Wysocki if (cb_type == DOCK_CALL_FIXUP) { 113edf5bf34SRafael J. Wysocki void (*fixup)(struct acpi_device *); 114edf5bf34SRafael J. Wysocki 115edf5bf34SRafael J. Wysocki fixup = adev->hp->fixup; 116edf5bf34SRafael J. Wysocki if (fixup) { 117edf5bf34SRafael J. Wysocki acpi_unlock_hp_context(); 118edf5bf34SRafael J. Wysocki fixup(adev); 119edf5bf34SRafael J. Wysocki return; 120edf5bf34SRafael J. Wysocki } 121be27b3dcSRafael J. Wysocki } else if (cb_type == DOCK_CALL_UEVENT) { 122be27b3dcSRafael J. Wysocki void (*uevent)(struct acpi_device *, u32); 123be27b3dcSRafael J. Wysocki 124be27b3dcSRafael J. Wysocki uevent = adev->hp->uevent; 125be27b3dcSRafael J. Wysocki if (uevent) { 126be27b3dcSRafael J. Wysocki acpi_unlock_hp_context(); 127be27b3dcSRafael J. Wysocki uevent(adev, event); 128be27b3dcSRafael J. Wysocki return; 129be27b3dcSRafael J. Wysocki } 130edf5bf34SRafael J. Wysocki } else { 131edf5bf34SRafael J. Wysocki int (*notify)(struct acpi_device *, u32); 132edf5bf34SRafael J. Wysocki 133be27b3dcSRafael J. Wysocki notify = adev->hp->notify; 134edf5bf34SRafael J. Wysocki if (notify) { 135edf5bf34SRafael J. Wysocki acpi_unlock_hp_context(); 136edf5bf34SRafael J. Wysocki notify(adev, event); 137edf5bf34SRafael J. Wysocki return; 138edf5bf34SRafael J. Wysocki } 139edf5bf34SRafael J. Wysocki } 140edf5bf34SRafael J. Wysocki 1412f16817dSRafael J. Wysocki out: 142edf5bf34SRafael J. Wysocki acpi_unlock_hp_context(); 143c8f7a62cSLen Brown } 144c8f7a62cSLen Brown 1451e2380cdSRafael J. Wysocki static struct dock_station *find_dock_station(acpi_handle handle) 1461e2380cdSRafael J. Wysocki { 1471e2380cdSRafael J. Wysocki struct dock_station *ds; 1481e2380cdSRafael J. Wysocki 1491e2380cdSRafael J. Wysocki list_for_each_entry(ds, &dock_stations, sibling) 1501e2380cdSRafael J. Wysocki if (ds->handle == handle) 1511e2380cdSRafael J. Wysocki return ds; 1521e2380cdSRafael J. Wysocki 1531e2380cdSRafael J. Wysocki return NULL; 1541e2380cdSRafael J. Wysocki } 1551e2380cdSRafael J. Wysocki 156c8f7a62cSLen Brown /** 157c8f7a62cSLen Brown * find_dock_dependent_device - get a device dependent on this dock 158c8f7a62cSLen Brown * @ds: the dock station 1593b52b21fSRafael J. Wysocki * @adev: ACPI device object to find. 160c8f7a62cSLen Brown * 161c8f7a62cSLen Brown * iterate over the dependent device list for this dock. If the 162c8f7a62cSLen Brown * dependent device matches the handle, return. 163c8f7a62cSLen Brown */ 164c8f7a62cSLen Brown static struct dock_dependent_device * 1653b52b21fSRafael J. Wysocki find_dock_dependent_device(struct dock_station *ds, struct acpi_device *adev) 166c8f7a62cSLen Brown { 167c8f7a62cSLen Brown struct dock_dependent_device *dd; 168c8f7a62cSLen Brown 169ed633e70SJiang Liu list_for_each_entry(dd, &ds->dependent_devices, list) 1703b52b21fSRafael J. Wysocki if (adev == dd->adev) 171c8f7a62cSLen Brown return dd; 172ed633e70SJiang Liu 173c8f7a62cSLen Brown return NULL; 174c8f7a62cSLen Brown } 175c8f7a62cSLen Brown 1761e2380cdSRafael J. Wysocki void register_dock_dependent_device(struct acpi_device *adev, 1771e2380cdSRafael J. Wysocki acpi_handle dshandle) 1781e2380cdSRafael J. Wysocki { 1791e2380cdSRafael J. Wysocki struct dock_station *ds = find_dock_station(dshandle); 1801e2380cdSRafael J. Wysocki 1813b52b21fSRafael J. Wysocki if (ds && !find_dock_dependent_device(ds, adev)) 1823b52b21fSRafael J. Wysocki add_dock_dependent_device(ds, adev); 1831e2380cdSRafael J. Wysocki } 1841e2380cdSRafael J. Wysocki 185c8f7a62cSLen Brown /***************************************************************************** 186c8f7a62cSLen Brown * Dock functions * 187c8f7a62cSLen Brown *****************************************************************************/ 188db350b08SShaohua Li 189c8f7a62cSLen Brown /** 190c8f7a62cSLen Brown * is_dock_device - see if a device is on a dock station 1913b52b21fSRafael J. Wysocki * @adev: ACPI device object to check. 192c8f7a62cSLen Brown * 193c8f7a62cSLen Brown * If this device is either the dock station itself, 194c8f7a62cSLen Brown * or is a device dependent on the dock station, then it 195c8f7a62cSLen Brown * is a dock device 196c8f7a62cSLen Brown */ 1973b52b21fSRafael J. Wysocki int is_dock_device(struct acpi_device *adev) 198c8f7a62cSLen Brown { 199db350b08SShaohua Li struct dock_station *dock_station; 200db350b08SShaohua Li 201db350b08SShaohua Li if (!dock_station_count) 202c8f7a62cSLen Brown return 0; 203c8f7a62cSLen Brown 2043b52b21fSRafael J. Wysocki if (acpi_dock_match(adev->handle)) 205c8f7a62cSLen Brown return 1; 206747479a3SAlex Chiang 207747479a3SAlex Chiang list_for_each_entry(dock_station, &dock_stations, sibling) 2083b52b21fSRafael J. Wysocki if (find_dock_dependent_device(dock_station, adev)) 209db350b08SShaohua Li return 1; 210c8f7a62cSLen Brown 211c8f7a62cSLen Brown return 0; 212c8f7a62cSLen Brown } 213c8f7a62cSLen Brown EXPORT_SYMBOL_GPL(is_dock_device); 214c8f7a62cSLen Brown 215c8f7a62cSLen Brown /** 216c8f7a62cSLen Brown * dock_present - see if the dock station is present. 217c8f7a62cSLen Brown * @ds: the dock station 218c8f7a62cSLen Brown * 219c8f7a62cSLen Brown * execute the _STA method. note that present does not 220c8f7a62cSLen Brown * imply that we are docked. 221c8f7a62cSLen Brown */ 222c8f7a62cSLen Brown static int dock_present(struct dock_station *ds) 223c8f7a62cSLen Brown { 22427663c58SMatthew Wilcox unsigned long long sta; 225c8f7a62cSLen Brown acpi_status status; 226c8f7a62cSLen Brown 227c8f7a62cSLen Brown if (ds) { 228c8f7a62cSLen Brown status = acpi_evaluate_integer(ds->handle, "_STA", NULL, &sta); 229c8f7a62cSLen Brown if (ACPI_SUCCESS(status) && sta) 230c8f7a62cSLen Brown return 1; 231c8f7a62cSLen Brown } 232c8f7a62cSLen Brown return 0; 233c8f7a62cSLen Brown } 234c8f7a62cSLen Brown 235c8f7a62cSLen Brown /** 23637f90877SRafael J. Wysocki * hot_remove_dock_devices - Remove dock station devices. 23737f90877SRafael J. Wysocki * @ds: Dock station. 23837f90877SRafael J. Wysocki */ 23937f90877SRafael J. Wysocki static void hot_remove_dock_devices(struct dock_station *ds) 24037f90877SRafael J. Wysocki { 24137f90877SRafael J. Wysocki struct dock_dependent_device *dd; 24237f90877SRafael J. Wysocki 24337f90877SRafael J. Wysocki /* 24437f90877SRafael J. Wysocki * Walk the list in reverse order so that devices that have been added 24537f90877SRafael J. Wysocki * last are removed first (in case there are some indirect dependencies 24637f90877SRafael J. Wysocki * between them). 24737f90877SRafael J. Wysocki */ 24837f90877SRafael J. Wysocki list_for_each_entry_reverse(dd, &ds->dependent_devices, list) 24937f90877SRafael J. Wysocki dock_hotplug_event(dd, ACPI_NOTIFY_EJECT_REQUEST, false); 25037f90877SRafael J. Wysocki 25137f90877SRafael J. Wysocki list_for_each_entry_reverse(dd, &ds->dependent_devices, list) 2523b52b21fSRafael J. Wysocki acpi_bus_trim(dd->adev); 25337f90877SRafael J. Wysocki } 25437f90877SRafael J. Wysocki 25537f90877SRafael J. Wysocki /** 25637f90877SRafael J. Wysocki * hotplug_dock_devices - Insert devices on a dock station. 257c8f7a62cSLen Brown * @ds: the dock station 25837f90877SRafael J. Wysocki * @event: either bus check or device check request 259c8f7a62cSLen Brown * 260c8f7a62cSLen Brown * Some devices on the dock station need to have drivers called 261c8f7a62cSLen Brown * to perform hotplug operations after a dock event has occurred. 262c8f7a62cSLen Brown * Traverse the list of dock devices that have registered a 263c8f7a62cSLen Brown * hotplug handler, and call the handler. 264c8f7a62cSLen Brown */ 265c8f7a62cSLen Brown static void hotplug_dock_devices(struct dock_station *ds, u32 event) 266c8f7a62cSLen Brown { 267c8f7a62cSLen Brown struct dock_dependent_device *dd; 268c8f7a62cSLen Brown 269f09ce741SRafael J. Wysocki /* Call driver specific post-dock fixups. */ 270f09ce741SRafael J. Wysocki list_for_each_entry(dd, &ds->dependent_devices, list) 271f09ce741SRafael J. Wysocki dock_hotplug_event(dd, event, DOCK_CALL_FIXUP); 272f09ce741SRafael J. Wysocki 27337f90877SRafael J. Wysocki /* Call driver specific hotplug functions. */ 27421a31013SRafael J. Wysocki list_for_each_entry(dd, &ds->dependent_devices, list) 275f09ce741SRafael J. Wysocki dock_hotplug_event(dd, event, DOCK_CALL_HANDLER); 276c8f7a62cSLen Brown 277c8f7a62cSLen Brown /* 2783b52b21fSRafael J. Wysocki * Check if all devices have been enumerated already. If not, run 2793b52b21fSRafael J. Wysocki * acpi_bus_scan() for them and that will cause scan handlers to be 2803b52b21fSRafael J. Wysocki * attached to device objects or acpi_drivers to be stopped/started if 2813b52b21fSRafael J. Wysocki * they are present. 282c8f7a62cSLen Brown */ 2833b52b21fSRafael J. Wysocki list_for_each_entry(dd, &ds->dependent_devices, list) { 2843b52b21fSRafael J. Wysocki struct acpi_device *adev = dd->adev; 2853b52b21fSRafael J. Wysocki 2863b52b21fSRafael J. Wysocki if (!acpi_device_enumerated(adev)) { 2873b52b21fSRafael J. Wysocki int ret = acpi_bus_scan(adev->handle); 2883b52b21fSRafael J. Wysocki if (ret) 2893b52b21fSRafael J. Wysocki dev_dbg(&adev->dev, "scan error %d\n", -ret); 2903b52b21fSRafael J. Wysocki } 2913b52b21fSRafael J. Wysocki } 292c8f7a62cSLen Brown } 293c8f7a62cSLen Brown 294c8f7a62cSLen Brown static void dock_event(struct dock_station *ds, u32 event, int num) 295c8f7a62cSLen Brown { 296db350b08SShaohua Li struct device *dev = &ds->dock_device->dev; 29766b56821SHolger Macht char event_string[13]; 29879a8f70bSKristen Carlson Accardi char *envp[] = { event_string, NULL }; 2991253f7aaSShaohua Li struct dock_dependent_device *dd; 30079a8f70bSKristen Carlson Accardi 30179a8f70bSKristen Carlson Accardi if (num == UNDOCK_EVENT) 30266b56821SHolger Macht sprintf(event_string, "EVENT=undock"); 30379a8f70bSKristen Carlson Accardi else 30466b56821SHolger Macht sprintf(event_string, "EVENT=dock"); 30579a8f70bSKristen Carlson Accardi 3065669021eSKristen Carlson Accardi /* 3078ea86e0bSKristen Carlson Accardi * Indicate that the status of the dock station has 3088ea86e0bSKristen Carlson Accardi * changed. 3095669021eSKristen Carlson Accardi */ 3101253f7aaSShaohua Li if (num == DOCK_EVENT) 3111253f7aaSShaohua Li kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); 3121253f7aaSShaohua Li 31321a31013SRafael J. Wysocki list_for_each_entry(dd, &ds->dependent_devices, list) 314f09ce741SRafael J. Wysocki dock_hotplug_event(dd, event, DOCK_CALL_UEVENT); 315747479a3SAlex Chiang 3161253f7aaSShaohua Li if (num != DOCK_EVENT) 31779a8f70bSKristen Carlson Accardi kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); 318c8f7a62cSLen Brown } 319c8f7a62cSLen Brown 320c8f7a62cSLen Brown /** 321c8f7a62cSLen Brown * handle_dock - handle a dock event 322c8f7a62cSLen Brown * @ds: the dock station 323c8f7a62cSLen Brown * @dock: to dock, or undock - that is the question 324c8f7a62cSLen Brown * 325c8f7a62cSLen Brown * Execute the _DCK method in response to an acpi event 326c8f7a62cSLen Brown */ 327c8f7a62cSLen Brown static void handle_dock(struct dock_station *ds, int dock) 328c8f7a62cSLen Brown { 329c8f7a62cSLen Brown acpi_status status; 330c8f7a62cSLen Brown struct acpi_object_list arg_list; 331c8f7a62cSLen Brown union acpi_object arg; 3326a868e17SZhang Rui unsigned long long value; 333c8f7a62cSLen Brown 334cd73018fSToshi Kani acpi_handle_info(ds->handle, "%s\n", dock ? "docking" : "undocking"); 335c8f7a62cSLen Brown 336c8f7a62cSLen Brown /* _DCK method has one argument */ 337c8f7a62cSLen Brown arg_list.count = 1; 338c8f7a62cSLen Brown arg_list.pointer = &arg; 339c8f7a62cSLen Brown arg.type = ACPI_TYPE_INTEGER; 340c8f7a62cSLen Brown arg.integer.value = dock; 3416a868e17SZhang Rui status = acpi_evaluate_integer(ds->handle, "_DCK", &arg_list, &value); 342db350b08SShaohua Li if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) 343cd73018fSToshi Kani acpi_handle_err(ds->handle, "Failed to execute _DCK (0x%x)\n", 344cd73018fSToshi Kani status); 345c8f7a62cSLen Brown } 346c8f7a62cSLen Brown 347c8f7a62cSLen Brown static inline void dock(struct dock_station *ds) 348c8f7a62cSLen Brown { 349c8f7a62cSLen Brown handle_dock(ds, 1); 350c8f7a62cSLen Brown } 351c8f7a62cSLen Brown 352c8f7a62cSLen Brown static inline void undock(struct dock_station *ds) 353c8f7a62cSLen Brown { 354c8f7a62cSLen Brown handle_dock(ds, 0); 355c8f7a62cSLen Brown } 356c8f7a62cSLen Brown 357c8f7a62cSLen Brown static inline void begin_dock(struct dock_station *ds) 358c8f7a62cSLen Brown { 359c8f7a62cSLen Brown ds->flags |= DOCK_DOCKING; 360c8f7a62cSLen Brown } 361c8f7a62cSLen Brown 362c8f7a62cSLen Brown static inline void complete_dock(struct dock_station *ds) 363c8f7a62cSLen Brown { 364c8f7a62cSLen Brown ds->flags &= ~(DOCK_DOCKING); 365c8f7a62cSLen Brown ds->last_dock_time = jiffies; 366c8f7a62cSLen Brown } 367c8f7a62cSLen Brown 368a0cd35fdSKristen Carlson Accardi static inline void begin_undock(struct dock_station *ds) 369a0cd35fdSKristen Carlson Accardi { 370a0cd35fdSKristen Carlson Accardi ds->flags |= DOCK_UNDOCKING; 371a0cd35fdSKristen Carlson Accardi } 372a0cd35fdSKristen Carlson Accardi 373a0cd35fdSKristen Carlson Accardi static inline void complete_undock(struct dock_station *ds) 374a0cd35fdSKristen Carlson Accardi { 375a0cd35fdSKristen Carlson Accardi ds->flags &= ~(DOCK_UNDOCKING); 376a0cd35fdSKristen Carlson Accardi } 377a0cd35fdSKristen Carlson Accardi 378c8f7a62cSLen Brown /** 379c8f7a62cSLen Brown * dock_in_progress - see if we are in the middle of handling a dock event 380c8f7a62cSLen Brown * @ds: the dock station 381c8f7a62cSLen Brown * 382c8f7a62cSLen Brown * Sometimes while docking, false dock events can be sent to the driver 383c8f7a62cSLen Brown * because good connections aren't made or some other reason. Ignore these 384c8f7a62cSLen Brown * if we are in the middle of doing something. 385c8f7a62cSLen Brown */ 386c8f7a62cSLen Brown static int dock_in_progress(struct dock_station *ds) 387c8f7a62cSLen Brown { 388c8f7a62cSLen Brown if ((ds->flags & DOCK_DOCKING) || 389c8f7a62cSLen Brown time_before(jiffies, (ds->last_dock_time + HZ))) 390c8f7a62cSLen Brown return 1; 391c8f7a62cSLen Brown return 0; 392c8f7a62cSLen Brown } 393c8f7a62cSLen Brown 394c8f7a62cSLen Brown /** 395c80fdbe8Sbrandon@ifup.org * handle_eject_request - handle an undock request checking for error conditions 396c80fdbe8Sbrandon@ifup.org * 397c80fdbe8Sbrandon@ifup.org * Check to make sure the dock device is still present, then undock and 398c80fdbe8Sbrandon@ifup.org * hotremove all the devices that may need removing. 399c80fdbe8Sbrandon@ifup.org */ 400c80fdbe8Sbrandon@ifup.org static int handle_eject_request(struct dock_station *ds, u32 event) 401c80fdbe8Sbrandon@ifup.org { 402c80fdbe8Sbrandon@ifup.org if (dock_in_progress(ds)) 403c80fdbe8Sbrandon@ifup.org return -EBUSY; 404c80fdbe8Sbrandon@ifup.org 405c80fdbe8Sbrandon@ifup.org /* 406c80fdbe8Sbrandon@ifup.org * here we need to generate the undock 407c80fdbe8Sbrandon@ifup.org * event prior to actually doing the undock 408c80fdbe8Sbrandon@ifup.org * so that the device struct still exists. 409afd7301dSHolger Macht * Also, even send the dock event if the 410afd7301dSHolger Macht * device is not present anymore 411c80fdbe8Sbrandon@ifup.org */ 412c80fdbe8Sbrandon@ifup.org dock_event(ds, event, UNDOCK_EVENT); 413afd7301dSHolger Macht 41437f90877SRafael J. Wysocki hot_remove_dock_devices(ds); 415c80fdbe8Sbrandon@ifup.org undock(ds); 416c9b5471fSJiang Liu acpi_evaluate_lck(ds->handle, 0); 417c9b5471fSJiang Liu acpi_evaluate_ej0(ds->handle); 418c80fdbe8Sbrandon@ifup.org if (dock_present(ds)) { 419cd73018fSToshi Kani acpi_handle_err(ds->handle, "Unable to undock!\n"); 420c80fdbe8Sbrandon@ifup.org return -EBUSY; 421c80fdbe8Sbrandon@ifup.org } 422a0cd35fdSKristen Carlson Accardi complete_undock(ds); 423c80fdbe8Sbrandon@ifup.org return 0; 424c80fdbe8Sbrandon@ifup.org } 425c80fdbe8Sbrandon@ifup.org 426c80fdbe8Sbrandon@ifup.org /** 4271e2380cdSRafael J. Wysocki * dock_notify - Handle ACPI dock notification. 4281e2380cdSRafael J. Wysocki * @adev: Dock station's ACPI device object. 4291e2380cdSRafael J. Wysocki * @event: Event code. 430c8f7a62cSLen Brown * 431c8f7a62cSLen Brown * If we are notified to dock, then check to see if the dock is 432c8f7a62cSLen Brown * present and then dock. Notify all drivers of the dock event, 433c80fdbe8Sbrandon@ifup.org * and then hotplug and devices that may need hotplugging. 434c8f7a62cSLen Brown */ 4351e2380cdSRafael J. Wysocki int dock_notify(struct acpi_device *adev, u32 event) 436c8f7a62cSLen Brown { 4371e2380cdSRafael J. Wysocki acpi_handle handle = adev->handle; 4381e2380cdSRafael J. Wysocki struct dock_station *ds = find_dock_station(handle); 439db350b08SShaohua Li int surprise_removal = 0; 440c8f7a62cSLen Brown 4411e2380cdSRafael J. Wysocki if (!ds) 4421e2380cdSRafael J. Wysocki return -ENODEV; 4431e2380cdSRafael J. Wysocki 444db350b08SShaohua Li /* 445db350b08SShaohua Li * According to acpi spec 3.0a, if a DEVICE_CHECK notification 446db350b08SShaohua Li * is sent and _DCK is present, it is assumed to mean an undock 447db350b08SShaohua Li * request. 448db350b08SShaohua Li */ 449db350b08SShaohua Li if ((ds->flags & DOCK_IS_DOCK) && event == ACPI_NOTIFY_DEVICE_CHECK) 450db350b08SShaohua Li event = ACPI_NOTIFY_EJECT_REQUEST; 451db350b08SShaohua Li 452db350b08SShaohua Li /* 453db350b08SShaohua Li * dock station: BUS_CHECK - docked or surprise removal 454db350b08SShaohua Li * DEVICE_CHECK - undocked 455db350b08SShaohua Li * other device: BUS_CHECK/DEVICE_CHECK - added or surprise removal 456db350b08SShaohua Li * 457db350b08SShaohua Li * To simplify event handling, dock dependent device handler always 458db350b08SShaohua Li * get ACPI_NOTIFY_BUS_CHECK/ACPI_NOTIFY_DEVICE_CHECK for add and 459db350b08SShaohua Li * ACPI_NOTIFY_EJECT_REQUEST for removal 460db350b08SShaohua Li */ 461c8f7a62cSLen Brown switch (event) { 462c8f7a62cSLen Brown case ACPI_NOTIFY_BUS_CHECK: 463db350b08SShaohua Li case ACPI_NOTIFY_DEVICE_CHECK: 4640a8e5c3dSRafael J. Wysocki if (!dock_in_progress(ds) && !acpi_device_enumerated(adev)) { 465c8f7a62cSLen Brown begin_dock(ds); 466c8f7a62cSLen Brown dock(ds); 467c8f7a62cSLen Brown if (!dock_present(ds)) { 468cd73018fSToshi Kani acpi_handle_err(handle, "Unable to dock!\n"); 4698b59560aSShaohua Li complete_dock(ds); 470c8f7a62cSLen Brown break; 471c8f7a62cSLen Brown } 472c8f7a62cSLen Brown hotplug_dock_devices(ds, event); 473c8f7a62cSLen Brown complete_dock(ds); 474c8f7a62cSLen Brown dock_event(ds, event, DOCK_EVENT); 475c9b5471fSJiang Liu acpi_evaluate_lck(ds->handle, 1); 4763a37898dSLin Ming acpi_update_all_gpes(); 477c8f7a62cSLen Brown break; 478db350b08SShaohua Li } 479db350b08SShaohua Li if (dock_present(ds) || dock_in_progress(ds)) 480db350b08SShaohua Li break; 481db350b08SShaohua Li /* This is a surprise removal */ 482db350b08SShaohua Li surprise_removal = 1; 483db350b08SShaohua Li event = ACPI_NOTIFY_EJECT_REQUEST; 484db350b08SShaohua Li /* Fall back */ 485c8f7a62cSLen Brown case ACPI_NOTIFY_EJECT_REQUEST: 486a0cd35fdSKristen Carlson Accardi begin_undock(ds); 487f730ae18SShaohua Li if ((immediate_undock && !(ds->flags & DOCK_IS_ATA)) 488f730ae18SShaohua Li || surprise_removal) 489c80fdbe8Sbrandon@ifup.org handle_eject_request(ds, event); 490a0cd35fdSKristen Carlson Accardi else 491a0cd35fdSKristen Carlson Accardi dock_event(ds, event, UNDOCK_EVENT); 492c8f7a62cSLen Brown break; 493c8f7a62cSLen Brown } 4941e2380cdSRafael J. Wysocki return 0; 495c8f7a62cSLen Brown } 496c8f7a62cSLen Brown 497c80fdbe8Sbrandon@ifup.org /* 498c80fdbe8Sbrandon@ifup.org * show_docked - read method for "docked" file in sysfs 499c80fdbe8Sbrandon@ifup.org */ 500c80fdbe8Sbrandon@ifup.org static ssize_t show_docked(struct device *dev, 501c80fdbe8Sbrandon@ifup.org struct device_attribute *attr, char *buf) 502c80fdbe8Sbrandon@ifup.org { 503fe06fba2SAlex Chiang struct dock_station *dock_station = dev->platform_data; 504ab62f9cdSRafael J. Wysocki struct acpi_device *adev = NULL; 505c80fdbe8Sbrandon@ifup.org 506ab62f9cdSRafael J. Wysocki acpi_bus_get_device(dock_station->handle, &adev); 507ab62f9cdSRafael J. Wysocki return snprintf(buf, PAGE_SIZE, "%u\n", acpi_device_enumerated(adev)); 508c80fdbe8Sbrandon@ifup.org } 509e5685b9dSAdrian Bunk static DEVICE_ATTR(docked, S_IRUGO, show_docked, NULL); 510c80fdbe8Sbrandon@ifup.org 511c80fdbe8Sbrandon@ifup.org /* 512a0cd35fdSKristen Carlson Accardi * show_flags - read method for flags file in sysfs 513a0cd35fdSKristen Carlson Accardi */ 514a0cd35fdSKristen Carlson Accardi static ssize_t show_flags(struct device *dev, 515a0cd35fdSKristen Carlson Accardi struct device_attribute *attr, char *buf) 516a0cd35fdSKristen Carlson Accardi { 517fe06fba2SAlex Chiang struct dock_station *dock_station = dev->platform_data; 518a0cd35fdSKristen Carlson Accardi return snprintf(buf, PAGE_SIZE, "%d\n", dock_station->flags); 519a0cd35fdSKristen Carlson Accardi 520a0cd35fdSKristen Carlson Accardi } 521e5685b9dSAdrian Bunk static DEVICE_ATTR(flags, S_IRUGO, show_flags, NULL); 522a0cd35fdSKristen Carlson Accardi 523a0cd35fdSKristen Carlson Accardi /* 524c80fdbe8Sbrandon@ifup.org * write_undock - write method for "undock" file in sysfs 525c80fdbe8Sbrandon@ifup.org */ 526c80fdbe8Sbrandon@ifup.org static ssize_t write_undock(struct device *dev, struct device_attribute *attr, 527c80fdbe8Sbrandon@ifup.org const char *buf, size_t count) 528c80fdbe8Sbrandon@ifup.org { 529c80fdbe8Sbrandon@ifup.org int ret; 530fe06fba2SAlex Chiang struct dock_station *dock_station = dev->platform_data; 531c80fdbe8Sbrandon@ifup.org 532c80fdbe8Sbrandon@ifup.org if (!count) 533c80fdbe8Sbrandon@ifup.org return -EINVAL; 534c80fdbe8Sbrandon@ifup.org 5358112006fSRafael J. Wysocki acpi_scan_lock_acquire(); 5369171f834SHolger Macht begin_undock(dock_station); 537c80fdbe8Sbrandon@ifup.org ret = handle_eject_request(dock_station, ACPI_NOTIFY_EJECT_REQUEST); 5388112006fSRafael J. Wysocki acpi_scan_lock_release(); 539c80fdbe8Sbrandon@ifup.org return ret ? ret: count; 540c80fdbe8Sbrandon@ifup.org } 541e5685b9dSAdrian Bunk static DEVICE_ATTR(undock, S_IWUSR, NULL, write_undock); 542c80fdbe8Sbrandon@ifup.org 543ac122bb6SIlya A. Volynets-Evenbakh /* 544ac122bb6SIlya A. Volynets-Evenbakh * show_dock_uid - read method for "uid" file in sysfs 545ac122bb6SIlya A. Volynets-Evenbakh */ 546ac122bb6SIlya A. Volynets-Evenbakh static ssize_t show_dock_uid(struct device *dev, 547ac122bb6SIlya A. Volynets-Evenbakh struct device_attribute *attr, char *buf) 548ac122bb6SIlya A. Volynets-Evenbakh { 54927663c58SMatthew Wilcox unsigned long long lbuf; 550fe06fba2SAlex Chiang struct dock_station *dock_station = dev->platform_data; 55138ff4ffcSKristen Carlson Accardi acpi_status status = acpi_evaluate_integer(dock_station->handle, 55238ff4ffcSKristen Carlson Accardi "_UID", NULL, &lbuf); 55338ff4ffcSKristen Carlson Accardi if (ACPI_FAILURE(status)) 554ac122bb6SIlya A. Volynets-Evenbakh return 0; 55538ff4ffcSKristen Carlson Accardi 55627663c58SMatthew Wilcox return snprintf(buf, PAGE_SIZE, "%llx\n", lbuf); 557ac122bb6SIlya A. Volynets-Evenbakh } 558e5685b9dSAdrian Bunk static DEVICE_ATTR(uid, S_IRUGO, show_dock_uid, NULL); 559ac122bb6SIlya A. Volynets-Evenbakh 5608652b00fSShaohua Li static ssize_t show_dock_type(struct device *dev, 5618652b00fSShaohua Li struct device_attribute *attr, char *buf) 5628652b00fSShaohua Li { 563fe06fba2SAlex Chiang struct dock_station *dock_station = dev->platform_data; 5648652b00fSShaohua Li char *type; 5658652b00fSShaohua Li 5668652b00fSShaohua Li if (dock_station->flags & DOCK_IS_DOCK) 5678652b00fSShaohua Li type = "dock_station"; 5688652b00fSShaohua Li else if (dock_station->flags & DOCK_IS_ATA) 5698652b00fSShaohua Li type = "ata_bay"; 5708652b00fSShaohua Li else if (dock_station->flags & DOCK_IS_BAT) 5718652b00fSShaohua Li type = "battery_bay"; 5728652b00fSShaohua Li else 5738652b00fSShaohua Li type = "unknown"; 5748652b00fSShaohua Li 5758652b00fSShaohua Li return snprintf(buf, PAGE_SIZE, "%s\n", type); 5768652b00fSShaohua Li } 5778652b00fSShaohua Li static DEVICE_ATTR(type, S_IRUGO, show_dock_type, NULL); 5788652b00fSShaohua Li 5795f46c2f2SAlex Chiang static struct attribute *dock_attributes[] = { 5805f46c2f2SAlex Chiang &dev_attr_docked.attr, 5815f46c2f2SAlex Chiang &dev_attr_flags.attr, 5825f46c2f2SAlex Chiang &dev_attr_undock.attr, 5835f46c2f2SAlex Chiang &dev_attr_uid.attr, 5845f46c2f2SAlex Chiang &dev_attr_type.attr, 5855f46c2f2SAlex Chiang NULL 5865f46c2f2SAlex Chiang }; 5875f46c2f2SAlex Chiang 58819dc7134SAmitoj Kaur Chawla static const struct attribute_group dock_attribute_group = { 5895f46c2f2SAlex Chiang .attrs = dock_attributes 5905f46c2f2SAlex Chiang }; 5915f46c2f2SAlex Chiang 592c8f7a62cSLen Brown /** 5931e2380cdSRafael J. Wysocki * acpi_dock_add - Add a new dock station 5941e2380cdSRafael J. Wysocki * @adev: Dock station ACPI device object. 595c8f7a62cSLen Brown * 5961e2380cdSRafael J. Wysocki * allocated and initialize a new dock station device. 597c8f7a62cSLen Brown */ 5981e2380cdSRafael J. Wysocki void acpi_dock_add(struct acpi_device *adev) 599c8f7a62cSLen Brown { 6002efbca4dSRafael J. Wysocki struct dock_station *dock_station, ds = { NULL, }; 601af887449SRafael J. Wysocki struct platform_device_info pdevinfo; 6021e2380cdSRafael J. Wysocki acpi_handle handle = adev->handle; 603747479a3SAlex Chiang struct platform_device *dd; 6042efbca4dSRafael J. Wysocki int ret; 605c8f7a62cSLen Brown 606af887449SRafael J. Wysocki memset(&pdevinfo, 0, sizeof(pdevinfo)); 607af887449SRafael J. Wysocki pdevinfo.name = "dock"; 608af887449SRafael J. Wysocki pdevinfo.id = dock_station_count; 609ce793486SRafael J. Wysocki pdevinfo.fwnode = acpi_fwnode_handle(adev); 610af887449SRafael J. Wysocki pdevinfo.data = &ds; 611af887449SRafael J. Wysocki pdevinfo.size_data = sizeof(ds); 612af887449SRafael J. Wysocki dd = platform_device_register_full(&pdevinfo); 613747479a3SAlex Chiang if (IS_ERR(dd)) 6141e2380cdSRafael J. Wysocki return; 6159751cb72SAlex Chiang 616747479a3SAlex Chiang dock_station = dd->dev.platform_data; 617747479a3SAlex Chiang 618c8f7a62cSLen Brown dock_station->handle = handle; 619747479a3SAlex Chiang dock_station->dock_device = dd; 620c8f7a62cSLen Brown dock_station->last_dock_time = jiffies - HZ; 621c8f7a62cSLen Brown 622747479a3SAlex Chiang INIT_LIST_HEAD(&dock_station->sibling); 623747479a3SAlex Chiang INIT_LIST_HEAD(&dock_station->dependent_devices); 624a0cd35fdSKristen Carlson Accardi 6259ef2a9a9SKristen Carlson Accardi /* we want the dock device to send uevents */ 626747479a3SAlex Chiang dev_set_uevent_suppress(&dd->dev, 0); 6279ef2a9a9SKristen Carlson Accardi 628c9b5471fSJiang Liu if (acpi_dock_match(handle)) 629db350b08SShaohua Li dock_station->flags |= DOCK_IS_DOCK; 630c9b5471fSJiang Liu if (acpi_ata_match(handle)) 631db350b08SShaohua Li dock_station->flags |= DOCK_IS_ATA; 632b43109faSRafael J. Wysocki if (acpi_device_is_battery(adev)) 633db350b08SShaohua Li dock_station->flags |= DOCK_IS_BAT; 634db350b08SShaohua Li 635747479a3SAlex Chiang ret = sysfs_create_group(&dd->dev.kobj, &dock_attribute_group); 6368652b00fSShaohua Li if (ret) 6375f46c2f2SAlex Chiang goto err_unregister; 638671adbecSKristen Carlson Accardi 639c8f7a62cSLen Brown /* add the dock station as a device dependent on itself */ 6403b52b21fSRafael J. Wysocki ret = add_dock_dependent_device(dock_station, adev); 641f69cfdd2SAlex Chiang if (ret) 6425f46c2f2SAlex Chiang goto err_rmgroup; 643c8f7a62cSLen Brown 644db350b08SShaohua Li dock_station_count++; 64550d716e4SAlex Chiang list_add(&dock_station->sibling, &dock_stations); 6461e2380cdSRafael J. Wysocki adev->flags.is_dock_station = true; 6471e2380cdSRafael J. Wysocki dev_info(&adev->dev, "ACPI dock station (docks/bays count: %d)\n", 6481e2380cdSRafael J. Wysocki dock_station_count); 6491e2380cdSRafael J. Wysocki return; 650c8f7a62cSLen Brown 6515f46c2f2SAlex Chiang err_rmgroup: 652747479a3SAlex Chiang sysfs_remove_group(&dd->dev.kobj, &dock_attribute_group); 653f311e1c4SRafael J. Wysocki 6545f46c2f2SAlex Chiang err_unregister: 655747479a3SAlex Chiang platform_device_unregister(dd); 656cd73018fSToshi Kani acpi_handle_err(handle, "%s encountered error %d\n", __func__, ret); 657c8f7a62cSLen Brown } 658