18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * dock.c - ACPI dock station driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2006, 2014, Intel Corp. 68c2ecf20Sopenharmony_ci * Author: Kristen Carlson Accardi <kristen.c.accardi@intel.com> 78c2ecf20Sopenharmony_ci * Rafael J. Wysocki <rafael.j.wysocki@intel.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/types.h> 158c2ecf20Sopenharmony_ci#include <linux/notifier.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 188c2ecf20Sopenharmony_ci#include <linux/stddef.h> 198c2ecf20Sopenharmony_ci#include <linux/acpi.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include "internal.h" 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic bool immediate_undock = 1; 248c2ecf20Sopenharmony_cimodule_param(immediate_undock, bool, 0644); 258c2ecf20Sopenharmony_ciMODULE_PARM_DESC(immediate_undock, "1 (default) will cause the driver to " 268c2ecf20Sopenharmony_ci "undock immediately when the undock button is pressed, 0 will cause" 278c2ecf20Sopenharmony_ci " the driver to wait for userspace to write the undock sysfs file " 288c2ecf20Sopenharmony_ci " before undocking"); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct dock_station { 318c2ecf20Sopenharmony_ci acpi_handle handle; 328c2ecf20Sopenharmony_ci unsigned long last_dock_time; 338c2ecf20Sopenharmony_ci u32 flags; 348c2ecf20Sopenharmony_ci struct list_head dependent_devices; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci struct list_head sibling; 378c2ecf20Sopenharmony_ci struct platform_device *dock_device; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_cistatic LIST_HEAD(dock_stations); 408c2ecf20Sopenharmony_cistatic int dock_station_count; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistruct dock_dependent_device { 438c2ecf20Sopenharmony_ci struct list_head list; 448c2ecf20Sopenharmony_ci struct acpi_device *adev; 458c2ecf20Sopenharmony_ci}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci#define DOCK_DOCKING 0x00000001 488c2ecf20Sopenharmony_ci#define DOCK_UNDOCKING 0x00000002 498c2ecf20Sopenharmony_ci#define DOCK_IS_DOCK 0x00000010 508c2ecf20Sopenharmony_ci#define DOCK_IS_ATA 0x00000020 518c2ecf20Sopenharmony_ci#define DOCK_IS_BAT 0x00000040 528c2ecf20Sopenharmony_ci#define DOCK_EVENT 3 538c2ecf20Sopenharmony_ci#define UNDOCK_EVENT 2 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cienum dock_callback_type { 568c2ecf20Sopenharmony_ci DOCK_CALL_HANDLER, 578c2ecf20Sopenharmony_ci DOCK_CALL_FIXUP, 588c2ecf20Sopenharmony_ci DOCK_CALL_UEVENT, 598c2ecf20Sopenharmony_ci}; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci/***************************************************************************** 628c2ecf20Sopenharmony_ci * Dock Dependent device functions * 638c2ecf20Sopenharmony_ci *****************************************************************************/ 648c2ecf20Sopenharmony_ci/** 658c2ecf20Sopenharmony_ci * add_dock_dependent_device - associate a device with the dock station 668c2ecf20Sopenharmony_ci * @ds: Dock station. 678c2ecf20Sopenharmony_ci * @adev: Dependent ACPI device object. 688c2ecf20Sopenharmony_ci * 698c2ecf20Sopenharmony_ci * Add the dependent device to the dock's dependent device list. 708c2ecf20Sopenharmony_ci */ 718c2ecf20Sopenharmony_cistatic int add_dock_dependent_device(struct dock_station *ds, 728c2ecf20Sopenharmony_ci struct acpi_device *adev) 738c2ecf20Sopenharmony_ci{ 748c2ecf20Sopenharmony_ci struct dock_dependent_device *dd; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci dd = kzalloc(sizeof(*dd), GFP_KERNEL); 778c2ecf20Sopenharmony_ci if (!dd) 788c2ecf20Sopenharmony_ci return -ENOMEM; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci dd->adev = adev; 818c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&dd->list); 828c2ecf20Sopenharmony_ci list_add_tail(&dd->list, &ds->dependent_devices); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci return 0; 858c2ecf20Sopenharmony_ci} 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic void dock_hotplug_event(struct dock_dependent_device *dd, u32 event, 888c2ecf20Sopenharmony_ci enum dock_callback_type cb_type) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci struct acpi_device *adev = dd->adev; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci acpi_lock_hp_context(); 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci if (!adev->hp) 958c2ecf20Sopenharmony_ci goto out; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (cb_type == DOCK_CALL_FIXUP) { 988c2ecf20Sopenharmony_ci void (*fixup)(struct acpi_device *); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci fixup = adev->hp->fixup; 1018c2ecf20Sopenharmony_ci if (fixup) { 1028c2ecf20Sopenharmony_ci acpi_unlock_hp_context(); 1038c2ecf20Sopenharmony_ci fixup(adev); 1048c2ecf20Sopenharmony_ci return; 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci } else if (cb_type == DOCK_CALL_UEVENT) { 1078c2ecf20Sopenharmony_ci void (*uevent)(struct acpi_device *, u32); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci uevent = adev->hp->uevent; 1108c2ecf20Sopenharmony_ci if (uevent) { 1118c2ecf20Sopenharmony_ci acpi_unlock_hp_context(); 1128c2ecf20Sopenharmony_ci uevent(adev, event); 1138c2ecf20Sopenharmony_ci return; 1148c2ecf20Sopenharmony_ci } 1158c2ecf20Sopenharmony_ci } else { 1168c2ecf20Sopenharmony_ci int (*notify)(struct acpi_device *, u32); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci notify = adev->hp->notify; 1198c2ecf20Sopenharmony_ci if (notify) { 1208c2ecf20Sopenharmony_ci acpi_unlock_hp_context(); 1218c2ecf20Sopenharmony_ci notify(adev, event); 1228c2ecf20Sopenharmony_ci return; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci out: 1278c2ecf20Sopenharmony_ci acpi_unlock_hp_context(); 1288c2ecf20Sopenharmony_ci} 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_cistatic struct dock_station *find_dock_station(acpi_handle handle) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci struct dock_station *ds; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci list_for_each_entry(ds, &dock_stations, sibling) 1358c2ecf20Sopenharmony_ci if (ds->handle == handle) 1368c2ecf20Sopenharmony_ci return ds; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci return NULL; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci/** 1428c2ecf20Sopenharmony_ci * find_dock_dependent_device - get a device dependent on this dock 1438c2ecf20Sopenharmony_ci * @ds: the dock station 1448c2ecf20Sopenharmony_ci * @adev: ACPI device object to find. 1458c2ecf20Sopenharmony_ci * 1468c2ecf20Sopenharmony_ci * iterate over the dependent device list for this dock. If the 1478c2ecf20Sopenharmony_ci * dependent device matches the handle, return. 1488c2ecf20Sopenharmony_ci */ 1498c2ecf20Sopenharmony_cistatic struct dock_dependent_device * 1508c2ecf20Sopenharmony_cifind_dock_dependent_device(struct dock_station *ds, struct acpi_device *adev) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci struct dock_dependent_device *dd; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci list_for_each_entry(dd, &ds->dependent_devices, list) 1558c2ecf20Sopenharmony_ci if (adev == dd->adev) 1568c2ecf20Sopenharmony_ci return dd; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci return NULL; 1598c2ecf20Sopenharmony_ci} 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_civoid register_dock_dependent_device(struct acpi_device *adev, 1628c2ecf20Sopenharmony_ci acpi_handle dshandle) 1638c2ecf20Sopenharmony_ci{ 1648c2ecf20Sopenharmony_ci struct dock_station *ds = find_dock_station(dshandle); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci if (ds && !find_dock_dependent_device(ds, adev)) 1678c2ecf20Sopenharmony_ci add_dock_dependent_device(ds, adev); 1688c2ecf20Sopenharmony_ci} 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci/***************************************************************************** 1718c2ecf20Sopenharmony_ci * Dock functions * 1728c2ecf20Sopenharmony_ci *****************************************************************************/ 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci/** 1758c2ecf20Sopenharmony_ci * is_dock_device - see if a device is on a dock station 1768c2ecf20Sopenharmony_ci * @adev: ACPI device object to check. 1778c2ecf20Sopenharmony_ci * 1788c2ecf20Sopenharmony_ci * If this device is either the dock station itself, 1798c2ecf20Sopenharmony_ci * or is a device dependent on the dock station, then it 1808c2ecf20Sopenharmony_ci * is a dock device 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_ciint is_dock_device(struct acpi_device *adev) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci struct dock_station *dock_station; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (!dock_station_count) 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci if (acpi_dock_match(adev->handle)) 1908c2ecf20Sopenharmony_ci return 1; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci list_for_each_entry(dock_station, &dock_stations, sibling) 1938c2ecf20Sopenharmony_ci if (find_dock_dependent_device(dock_station, adev)) 1948c2ecf20Sopenharmony_ci return 1; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(is_dock_device); 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci/** 2018c2ecf20Sopenharmony_ci * dock_present - see if the dock station is present. 2028c2ecf20Sopenharmony_ci * @ds: the dock station 2038c2ecf20Sopenharmony_ci * 2048c2ecf20Sopenharmony_ci * execute the _STA method. note that present does not 2058c2ecf20Sopenharmony_ci * imply that we are docked. 2068c2ecf20Sopenharmony_ci */ 2078c2ecf20Sopenharmony_cistatic int dock_present(struct dock_station *ds) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci unsigned long long sta; 2108c2ecf20Sopenharmony_ci acpi_status status; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci if (ds) { 2138c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(ds->handle, "_STA", NULL, &sta); 2148c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(status) && sta) 2158c2ecf20Sopenharmony_ci return 1; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci return 0; 2188c2ecf20Sopenharmony_ci} 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci/** 2218c2ecf20Sopenharmony_ci * hot_remove_dock_devices - Remove dock station devices. 2228c2ecf20Sopenharmony_ci * @ds: Dock station. 2238c2ecf20Sopenharmony_ci */ 2248c2ecf20Sopenharmony_cistatic void hot_remove_dock_devices(struct dock_station *ds) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci struct dock_dependent_device *dd; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci /* 2298c2ecf20Sopenharmony_ci * Walk the list in reverse order so that devices that have been added 2308c2ecf20Sopenharmony_ci * last are removed first (in case there are some indirect dependencies 2318c2ecf20Sopenharmony_ci * between them). 2328c2ecf20Sopenharmony_ci */ 2338c2ecf20Sopenharmony_ci list_for_each_entry_reverse(dd, &ds->dependent_devices, list) 2348c2ecf20Sopenharmony_ci dock_hotplug_event(dd, ACPI_NOTIFY_EJECT_REQUEST, 2358c2ecf20Sopenharmony_ci DOCK_CALL_HANDLER); 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci list_for_each_entry_reverse(dd, &ds->dependent_devices, list) 2388c2ecf20Sopenharmony_ci acpi_bus_trim(dd->adev); 2398c2ecf20Sopenharmony_ci} 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci/** 2428c2ecf20Sopenharmony_ci * hotplug_dock_devices - Insert devices on a dock station. 2438c2ecf20Sopenharmony_ci * @ds: the dock station 2448c2ecf20Sopenharmony_ci * @event: either bus check or device check request 2458c2ecf20Sopenharmony_ci * 2468c2ecf20Sopenharmony_ci * Some devices on the dock station need to have drivers called 2478c2ecf20Sopenharmony_ci * to perform hotplug operations after a dock event has occurred. 2488c2ecf20Sopenharmony_ci * Traverse the list of dock devices that have registered a 2498c2ecf20Sopenharmony_ci * hotplug handler, and call the handler. 2508c2ecf20Sopenharmony_ci */ 2518c2ecf20Sopenharmony_cistatic void hotplug_dock_devices(struct dock_station *ds, u32 event) 2528c2ecf20Sopenharmony_ci{ 2538c2ecf20Sopenharmony_ci struct dock_dependent_device *dd; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci /* Call driver specific post-dock fixups. */ 2568c2ecf20Sopenharmony_ci list_for_each_entry(dd, &ds->dependent_devices, list) 2578c2ecf20Sopenharmony_ci dock_hotplug_event(dd, event, DOCK_CALL_FIXUP); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci /* Call driver specific hotplug functions. */ 2608c2ecf20Sopenharmony_ci list_for_each_entry(dd, &ds->dependent_devices, list) 2618c2ecf20Sopenharmony_ci dock_hotplug_event(dd, event, DOCK_CALL_HANDLER); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci /* 2648c2ecf20Sopenharmony_ci * Check if all devices have been enumerated already. If not, run 2658c2ecf20Sopenharmony_ci * acpi_bus_scan() for them and that will cause scan handlers to be 2668c2ecf20Sopenharmony_ci * attached to device objects or acpi_drivers to be stopped/started if 2678c2ecf20Sopenharmony_ci * they are present. 2688c2ecf20Sopenharmony_ci */ 2698c2ecf20Sopenharmony_ci list_for_each_entry(dd, &ds->dependent_devices, list) { 2708c2ecf20Sopenharmony_ci struct acpi_device *adev = dd->adev; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci if (!acpi_device_enumerated(adev)) { 2738c2ecf20Sopenharmony_ci int ret = acpi_bus_scan(adev->handle); 2748c2ecf20Sopenharmony_ci if (ret) 2758c2ecf20Sopenharmony_ci dev_dbg(&adev->dev, "scan error %d\n", -ret); 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci } 2788c2ecf20Sopenharmony_ci} 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic void dock_event(struct dock_station *ds, u32 event, int num) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci struct device *dev = &ds->dock_device->dev; 2838c2ecf20Sopenharmony_ci char event_string[13]; 2848c2ecf20Sopenharmony_ci char *envp[] = { event_string, NULL }; 2858c2ecf20Sopenharmony_ci struct dock_dependent_device *dd; 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_ci if (num == UNDOCK_EVENT) 2888c2ecf20Sopenharmony_ci sprintf(event_string, "EVENT=undock"); 2898c2ecf20Sopenharmony_ci else 2908c2ecf20Sopenharmony_ci sprintf(event_string, "EVENT=dock"); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci /* 2938c2ecf20Sopenharmony_ci * Indicate that the status of the dock station has 2948c2ecf20Sopenharmony_ci * changed. 2958c2ecf20Sopenharmony_ci */ 2968c2ecf20Sopenharmony_ci if (num == DOCK_EVENT) 2978c2ecf20Sopenharmony_ci kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci list_for_each_entry(dd, &ds->dependent_devices, list) 3008c2ecf20Sopenharmony_ci dock_hotplug_event(dd, event, DOCK_CALL_UEVENT); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci if (num != DOCK_EVENT) 3038c2ecf20Sopenharmony_ci kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ci/** 3078c2ecf20Sopenharmony_ci * handle_dock - handle a dock event 3088c2ecf20Sopenharmony_ci * @ds: the dock station 3098c2ecf20Sopenharmony_ci * @dock: to dock, or undock - that is the question 3108c2ecf20Sopenharmony_ci * 3118c2ecf20Sopenharmony_ci * Execute the _DCK method in response to an acpi event 3128c2ecf20Sopenharmony_ci */ 3138c2ecf20Sopenharmony_cistatic void handle_dock(struct dock_station *ds, int dock) 3148c2ecf20Sopenharmony_ci{ 3158c2ecf20Sopenharmony_ci acpi_status status; 3168c2ecf20Sopenharmony_ci struct acpi_object_list arg_list; 3178c2ecf20Sopenharmony_ci union acpi_object arg; 3188c2ecf20Sopenharmony_ci unsigned long long value; 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci acpi_handle_info(ds->handle, "%s\n", dock ? "docking" : "undocking"); 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci /* _DCK method has one argument */ 3238c2ecf20Sopenharmony_ci arg_list.count = 1; 3248c2ecf20Sopenharmony_ci arg_list.pointer = &arg; 3258c2ecf20Sopenharmony_ci arg.type = ACPI_TYPE_INTEGER; 3268c2ecf20Sopenharmony_ci arg.integer.value = dock; 3278c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(ds->handle, "_DCK", &arg_list, &value); 3288c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) 3298c2ecf20Sopenharmony_ci acpi_handle_err(ds->handle, "Failed to execute _DCK (0x%x)\n", 3308c2ecf20Sopenharmony_ci status); 3318c2ecf20Sopenharmony_ci} 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_cistatic inline void dock(struct dock_station *ds) 3348c2ecf20Sopenharmony_ci{ 3358c2ecf20Sopenharmony_ci handle_dock(ds, 1); 3368c2ecf20Sopenharmony_ci} 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_cistatic inline void undock(struct dock_station *ds) 3398c2ecf20Sopenharmony_ci{ 3408c2ecf20Sopenharmony_ci handle_dock(ds, 0); 3418c2ecf20Sopenharmony_ci} 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_cistatic inline void begin_dock(struct dock_station *ds) 3448c2ecf20Sopenharmony_ci{ 3458c2ecf20Sopenharmony_ci ds->flags |= DOCK_DOCKING; 3468c2ecf20Sopenharmony_ci} 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_cistatic inline void complete_dock(struct dock_station *ds) 3498c2ecf20Sopenharmony_ci{ 3508c2ecf20Sopenharmony_ci ds->flags &= ~(DOCK_DOCKING); 3518c2ecf20Sopenharmony_ci ds->last_dock_time = jiffies; 3528c2ecf20Sopenharmony_ci} 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_cistatic inline void begin_undock(struct dock_station *ds) 3558c2ecf20Sopenharmony_ci{ 3568c2ecf20Sopenharmony_ci ds->flags |= DOCK_UNDOCKING; 3578c2ecf20Sopenharmony_ci} 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_cistatic inline void complete_undock(struct dock_station *ds) 3608c2ecf20Sopenharmony_ci{ 3618c2ecf20Sopenharmony_ci ds->flags &= ~(DOCK_UNDOCKING); 3628c2ecf20Sopenharmony_ci} 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci/** 3658c2ecf20Sopenharmony_ci * dock_in_progress - see if we are in the middle of handling a dock event 3668c2ecf20Sopenharmony_ci * @ds: the dock station 3678c2ecf20Sopenharmony_ci * 3688c2ecf20Sopenharmony_ci * Sometimes while docking, false dock events can be sent to the driver 3698c2ecf20Sopenharmony_ci * because good connections aren't made or some other reason. Ignore these 3708c2ecf20Sopenharmony_ci * if we are in the middle of doing something. 3718c2ecf20Sopenharmony_ci */ 3728c2ecf20Sopenharmony_cistatic int dock_in_progress(struct dock_station *ds) 3738c2ecf20Sopenharmony_ci{ 3748c2ecf20Sopenharmony_ci if ((ds->flags & DOCK_DOCKING) || 3758c2ecf20Sopenharmony_ci time_before(jiffies, (ds->last_dock_time + HZ))) 3768c2ecf20Sopenharmony_ci return 1; 3778c2ecf20Sopenharmony_ci return 0; 3788c2ecf20Sopenharmony_ci} 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci/** 3818c2ecf20Sopenharmony_ci * handle_eject_request - handle an undock request checking for error conditions 3828c2ecf20Sopenharmony_ci * 3838c2ecf20Sopenharmony_ci * Check to make sure the dock device is still present, then undock and 3848c2ecf20Sopenharmony_ci * hotremove all the devices that may need removing. 3858c2ecf20Sopenharmony_ci */ 3868c2ecf20Sopenharmony_cistatic int handle_eject_request(struct dock_station *ds, u32 event) 3878c2ecf20Sopenharmony_ci{ 3888c2ecf20Sopenharmony_ci if (dock_in_progress(ds)) 3898c2ecf20Sopenharmony_ci return -EBUSY; 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci /* 3928c2ecf20Sopenharmony_ci * here we need to generate the undock 3938c2ecf20Sopenharmony_ci * event prior to actually doing the undock 3948c2ecf20Sopenharmony_ci * so that the device struct still exists. 3958c2ecf20Sopenharmony_ci * Also, even send the dock event if the 3968c2ecf20Sopenharmony_ci * device is not present anymore 3978c2ecf20Sopenharmony_ci */ 3988c2ecf20Sopenharmony_ci dock_event(ds, event, UNDOCK_EVENT); 3998c2ecf20Sopenharmony_ci 4008c2ecf20Sopenharmony_ci hot_remove_dock_devices(ds); 4018c2ecf20Sopenharmony_ci undock(ds); 4028c2ecf20Sopenharmony_ci acpi_evaluate_lck(ds->handle, 0); 4038c2ecf20Sopenharmony_ci acpi_evaluate_ej0(ds->handle); 4048c2ecf20Sopenharmony_ci if (dock_present(ds)) { 4058c2ecf20Sopenharmony_ci acpi_handle_err(ds->handle, "Unable to undock!\n"); 4068c2ecf20Sopenharmony_ci return -EBUSY; 4078c2ecf20Sopenharmony_ci } 4088c2ecf20Sopenharmony_ci complete_undock(ds); 4098c2ecf20Sopenharmony_ci return 0; 4108c2ecf20Sopenharmony_ci} 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_ci/** 4138c2ecf20Sopenharmony_ci * dock_notify - Handle ACPI dock notification. 4148c2ecf20Sopenharmony_ci * @adev: Dock station's ACPI device object. 4158c2ecf20Sopenharmony_ci * @event: Event code. 4168c2ecf20Sopenharmony_ci * 4178c2ecf20Sopenharmony_ci * If we are notified to dock, then check to see if the dock is 4188c2ecf20Sopenharmony_ci * present and then dock. Notify all drivers of the dock event, 4198c2ecf20Sopenharmony_ci * and then hotplug and devices that may need hotplugging. 4208c2ecf20Sopenharmony_ci */ 4218c2ecf20Sopenharmony_ciint dock_notify(struct acpi_device *adev, u32 event) 4228c2ecf20Sopenharmony_ci{ 4238c2ecf20Sopenharmony_ci acpi_handle handle = adev->handle; 4248c2ecf20Sopenharmony_ci struct dock_station *ds = find_dock_station(handle); 4258c2ecf20Sopenharmony_ci int surprise_removal = 0; 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_ci if (!ds) 4288c2ecf20Sopenharmony_ci return -ENODEV; 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_ci /* 4318c2ecf20Sopenharmony_ci * According to acpi spec 3.0a, if a DEVICE_CHECK notification 4328c2ecf20Sopenharmony_ci * is sent and _DCK is present, it is assumed to mean an undock 4338c2ecf20Sopenharmony_ci * request. 4348c2ecf20Sopenharmony_ci */ 4358c2ecf20Sopenharmony_ci if ((ds->flags & DOCK_IS_DOCK) && event == ACPI_NOTIFY_DEVICE_CHECK) 4368c2ecf20Sopenharmony_ci event = ACPI_NOTIFY_EJECT_REQUEST; 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci /* 4398c2ecf20Sopenharmony_ci * dock station: BUS_CHECK - docked or surprise removal 4408c2ecf20Sopenharmony_ci * DEVICE_CHECK - undocked 4418c2ecf20Sopenharmony_ci * other device: BUS_CHECK/DEVICE_CHECK - added or surprise removal 4428c2ecf20Sopenharmony_ci * 4438c2ecf20Sopenharmony_ci * To simplify event handling, dock dependent device handler always 4448c2ecf20Sopenharmony_ci * get ACPI_NOTIFY_BUS_CHECK/ACPI_NOTIFY_DEVICE_CHECK for add and 4458c2ecf20Sopenharmony_ci * ACPI_NOTIFY_EJECT_REQUEST for removal 4468c2ecf20Sopenharmony_ci */ 4478c2ecf20Sopenharmony_ci switch (event) { 4488c2ecf20Sopenharmony_ci case ACPI_NOTIFY_BUS_CHECK: 4498c2ecf20Sopenharmony_ci case ACPI_NOTIFY_DEVICE_CHECK: 4508c2ecf20Sopenharmony_ci if (!dock_in_progress(ds) && !acpi_device_enumerated(adev)) { 4518c2ecf20Sopenharmony_ci begin_dock(ds); 4528c2ecf20Sopenharmony_ci dock(ds); 4538c2ecf20Sopenharmony_ci if (!dock_present(ds)) { 4548c2ecf20Sopenharmony_ci acpi_handle_err(handle, "Unable to dock!\n"); 4558c2ecf20Sopenharmony_ci complete_dock(ds); 4568c2ecf20Sopenharmony_ci break; 4578c2ecf20Sopenharmony_ci } 4588c2ecf20Sopenharmony_ci hotplug_dock_devices(ds, event); 4598c2ecf20Sopenharmony_ci complete_dock(ds); 4608c2ecf20Sopenharmony_ci dock_event(ds, event, DOCK_EVENT); 4618c2ecf20Sopenharmony_ci acpi_evaluate_lck(ds->handle, 1); 4628c2ecf20Sopenharmony_ci acpi_update_all_gpes(); 4638c2ecf20Sopenharmony_ci break; 4648c2ecf20Sopenharmony_ci } 4658c2ecf20Sopenharmony_ci if (dock_present(ds) || dock_in_progress(ds)) 4668c2ecf20Sopenharmony_ci break; 4678c2ecf20Sopenharmony_ci /* This is a surprise removal */ 4688c2ecf20Sopenharmony_ci surprise_removal = 1; 4698c2ecf20Sopenharmony_ci event = ACPI_NOTIFY_EJECT_REQUEST; 4708c2ecf20Sopenharmony_ci /* Fall back */ 4718c2ecf20Sopenharmony_ci fallthrough; 4728c2ecf20Sopenharmony_ci case ACPI_NOTIFY_EJECT_REQUEST: 4738c2ecf20Sopenharmony_ci begin_undock(ds); 4748c2ecf20Sopenharmony_ci if ((immediate_undock && !(ds->flags & DOCK_IS_ATA)) 4758c2ecf20Sopenharmony_ci || surprise_removal) 4768c2ecf20Sopenharmony_ci handle_eject_request(ds, event); 4778c2ecf20Sopenharmony_ci else 4788c2ecf20Sopenharmony_ci dock_event(ds, event, UNDOCK_EVENT); 4798c2ecf20Sopenharmony_ci break; 4808c2ecf20Sopenharmony_ci } 4818c2ecf20Sopenharmony_ci return 0; 4828c2ecf20Sopenharmony_ci} 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci/* 4858c2ecf20Sopenharmony_ci * show_docked - read method for "docked" file in sysfs 4868c2ecf20Sopenharmony_ci */ 4878c2ecf20Sopenharmony_cistatic ssize_t docked_show(struct device *dev, 4888c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 4898c2ecf20Sopenharmony_ci{ 4908c2ecf20Sopenharmony_ci struct dock_station *dock_station = dev->platform_data; 4918c2ecf20Sopenharmony_ci struct acpi_device *adev = NULL; 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci acpi_bus_get_device(dock_station->handle, &adev); 4948c2ecf20Sopenharmony_ci return snprintf(buf, PAGE_SIZE, "%u\n", acpi_device_enumerated(adev)); 4958c2ecf20Sopenharmony_ci} 4968c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(docked); 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci/* 4998c2ecf20Sopenharmony_ci * show_flags - read method for flags file in sysfs 5008c2ecf20Sopenharmony_ci */ 5018c2ecf20Sopenharmony_cistatic ssize_t flags_show(struct device *dev, 5028c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 5038c2ecf20Sopenharmony_ci{ 5048c2ecf20Sopenharmony_ci struct dock_station *dock_station = dev->platform_data; 5058c2ecf20Sopenharmony_ci return snprintf(buf, PAGE_SIZE, "%d\n", dock_station->flags); 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci} 5088c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(flags); 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci/* 5118c2ecf20Sopenharmony_ci * write_undock - write method for "undock" file in sysfs 5128c2ecf20Sopenharmony_ci */ 5138c2ecf20Sopenharmony_cistatic ssize_t undock_store(struct device *dev, struct device_attribute *attr, 5148c2ecf20Sopenharmony_ci const char *buf, size_t count) 5158c2ecf20Sopenharmony_ci{ 5168c2ecf20Sopenharmony_ci int ret; 5178c2ecf20Sopenharmony_ci struct dock_station *dock_station = dev->platform_data; 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci if (!count) 5208c2ecf20Sopenharmony_ci return -EINVAL; 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci acpi_scan_lock_acquire(); 5238c2ecf20Sopenharmony_ci begin_undock(dock_station); 5248c2ecf20Sopenharmony_ci ret = handle_eject_request(dock_station, ACPI_NOTIFY_EJECT_REQUEST); 5258c2ecf20Sopenharmony_ci acpi_scan_lock_release(); 5268c2ecf20Sopenharmony_ci return ret ? ret: count; 5278c2ecf20Sopenharmony_ci} 5288c2ecf20Sopenharmony_cistatic DEVICE_ATTR_WO(undock); 5298c2ecf20Sopenharmony_ci 5308c2ecf20Sopenharmony_ci/* 5318c2ecf20Sopenharmony_ci * show_dock_uid - read method for "uid" file in sysfs 5328c2ecf20Sopenharmony_ci */ 5338c2ecf20Sopenharmony_cistatic ssize_t uid_show(struct device *dev, 5348c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 5358c2ecf20Sopenharmony_ci{ 5368c2ecf20Sopenharmony_ci unsigned long long lbuf; 5378c2ecf20Sopenharmony_ci struct dock_station *dock_station = dev->platform_data; 5388c2ecf20Sopenharmony_ci acpi_status status = acpi_evaluate_integer(dock_station->handle, 5398c2ecf20Sopenharmony_ci "_UID", NULL, &lbuf); 5408c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 5418c2ecf20Sopenharmony_ci return 0; 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci return snprintf(buf, PAGE_SIZE, "%llx\n", lbuf); 5448c2ecf20Sopenharmony_ci} 5458c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(uid); 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_cistatic ssize_t type_show(struct device *dev, 5488c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 5498c2ecf20Sopenharmony_ci{ 5508c2ecf20Sopenharmony_ci struct dock_station *dock_station = dev->platform_data; 5518c2ecf20Sopenharmony_ci char *type; 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci if (dock_station->flags & DOCK_IS_DOCK) 5548c2ecf20Sopenharmony_ci type = "dock_station"; 5558c2ecf20Sopenharmony_ci else if (dock_station->flags & DOCK_IS_ATA) 5568c2ecf20Sopenharmony_ci type = "ata_bay"; 5578c2ecf20Sopenharmony_ci else if (dock_station->flags & DOCK_IS_BAT) 5588c2ecf20Sopenharmony_ci type = "battery_bay"; 5598c2ecf20Sopenharmony_ci else 5608c2ecf20Sopenharmony_ci type = "unknown"; 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_ci return snprintf(buf, PAGE_SIZE, "%s\n", type); 5638c2ecf20Sopenharmony_ci} 5648c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(type); 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_cistatic struct attribute *dock_attributes[] = { 5678c2ecf20Sopenharmony_ci &dev_attr_docked.attr, 5688c2ecf20Sopenharmony_ci &dev_attr_flags.attr, 5698c2ecf20Sopenharmony_ci &dev_attr_undock.attr, 5708c2ecf20Sopenharmony_ci &dev_attr_uid.attr, 5718c2ecf20Sopenharmony_ci &dev_attr_type.attr, 5728c2ecf20Sopenharmony_ci NULL 5738c2ecf20Sopenharmony_ci}; 5748c2ecf20Sopenharmony_ci 5758c2ecf20Sopenharmony_cistatic const struct attribute_group dock_attribute_group = { 5768c2ecf20Sopenharmony_ci .attrs = dock_attributes 5778c2ecf20Sopenharmony_ci}; 5788c2ecf20Sopenharmony_ci 5798c2ecf20Sopenharmony_ci/** 5808c2ecf20Sopenharmony_ci * acpi_dock_add - Add a new dock station 5818c2ecf20Sopenharmony_ci * @adev: Dock station ACPI device object. 5828c2ecf20Sopenharmony_ci * 5838c2ecf20Sopenharmony_ci * allocated and initialize a new dock station device. 5848c2ecf20Sopenharmony_ci */ 5858c2ecf20Sopenharmony_civoid acpi_dock_add(struct acpi_device *adev) 5868c2ecf20Sopenharmony_ci{ 5878c2ecf20Sopenharmony_ci struct dock_station *dock_station, ds = { NULL, }; 5888c2ecf20Sopenharmony_ci struct platform_device_info pdevinfo; 5898c2ecf20Sopenharmony_ci acpi_handle handle = adev->handle; 5908c2ecf20Sopenharmony_ci struct platform_device *dd; 5918c2ecf20Sopenharmony_ci int ret; 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci memset(&pdevinfo, 0, sizeof(pdevinfo)); 5948c2ecf20Sopenharmony_ci pdevinfo.name = "dock"; 5958c2ecf20Sopenharmony_ci pdevinfo.id = dock_station_count; 5968c2ecf20Sopenharmony_ci pdevinfo.fwnode = acpi_fwnode_handle(adev); 5978c2ecf20Sopenharmony_ci pdevinfo.data = &ds; 5988c2ecf20Sopenharmony_ci pdevinfo.size_data = sizeof(ds); 5998c2ecf20Sopenharmony_ci dd = platform_device_register_full(&pdevinfo); 6008c2ecf20Sopenharmony_ci if (IS_ERR(dd)) 6018c2ecf20Sopenharmony_ci return; 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci dock_station = dd->dev.platform_data; 6048c2ecf20Sopenharmony_ci 6058c2ecf20Sopenharmony_ci dock_station->handle = handle; 6068c2ecf20Sopenharmony_ci dock_station->dock_device = dd; 6078c2ecf20Sopenharmony_ci dock_station->last_dock_time = jiffies - HZ; 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&dock_station->sibling); 6108c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&dock_station->dependent_devices); 6118c2ecf20Sopenharmony_ci 6128c2ecf20Sopenharmony_ci /* we want the dock device to send uevents */ 6138c2ecf20Sopenharmony_ci dev_set_uevent_suppress(&dd->dev, 0); 6148c2ecf20Sopenharmony_ci 6158c2ecf20Sopenharmony_ci if (acpi_dock_match(handle)) 6168c2ecf20Sopenharmony_ci dock_station->flags |= DOCK_IS_DOCK; 6178c2ecf20Sopenharmony_ci if (acpi_ata_match(handle)) 6188c2ecf20Sopenharmony_ci dock_station->flags |= DOCK_IS_ATA; 6198c2ecf20Sopenharmony_ci if (acpi_device_is_battery(adev)) 6208c2ecf20Sopenharmony_ci dock_station->flags |= DOCK_IS_BAT; 6218c2ecf20Sopenharmony_ci 6228c2ecf20Sopenharmony_ci ret = sysfs_create_group(&dd->dev.kobj, &dock_attribute_group); 6238c2ecf20Sopenharmony_ci if (ret) 6248c2ecf20Sopenharmony_ci goto err_unregister; 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci /* add the dock station as a device dependent on itself */ 6278c2ecf20Sopenharmony_ci ret = add_dock_dependent_device(dock_station, adev); 6288c2ecf20Sopenharmony_ci if (ret) 6298c2ecf20Sopenharmony_ci goto err_rmgroup; 6308c2ecf20Sopenharmony_ci 6318c2ecf20Sopenharmony_ci dock_station_count++; 6328c2ecf20Sopenharmony_ci list_add(&dock_station->sibling, &dock_stations); 6338c2ecf20Sopenharmony_ci adev->flags.is_dock_station = true; 6348c2ecf20Sopenharmony_ci dev_info(&adev->dev, "ACPI dock station (docks/bays count: %d)\n", 6358c2ecf20Sopenharmony_ci dock_station_count); 6368c2ecf20Sopenharmony_ci return; 6378c2ecf20Sopenharmony_ci 6388c2ecf20Sopenharmony_cierr_rmgroup: 6398c2ecf20Sopenharmony_ci sysfs_remove_group(&dd->dev.kobj, &dock_attribute_group); 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_cierr_unregister: 6428c2ecf20Sopenharmony_ci platform_device_unregister(dd); 6438c2ecf20Sopenharmony_ci acpi_handle_err(handle, "%s encountered error %d\n", __func__, ret); 6448c2ecf20Sopenharmony_ci} 645