| /* | 
 |  * ACPI PCI HotPlug dock functions to ACPI CA subsystem | 
 |  * | 
 |  * Copyright (C) 2006 Kristen Carlson Accardi (kristen.c.accardi@intel.com) | 
 |  * Copyright (C) 2006 Intel Corporation | 
 |  * | 
 |  * All rights reserved. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License as published by | 
 |  * the Free Software Foundation; either version 2 of the License, or (at | 
 |  * your option) any later version. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, but | 
 |  * WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | 
 |  * NON INFRINGEMENT.  See the GNU General Public License for more | 
 |  * details. | 
 |  * | 
 |  * You should have received a copy of the GNU General Public License | 
 |  * along with this program; if not, write to the Free Software | 
 |  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | 
 |  * | 
 |  * Send feedback to <kristen.c.accardi@intel.com> | 
 |  * | 
 |  */ | 
 | #include <linux/init.h> | 
 | #include <linux/module.h> | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/pci.h> | 
 | #include <linux/smp_lock.h> | 
 | #include <linux/mutex.h> | 
 |  | 
 | #include "../pci.h" | 
 | #include "pci_hotplug.h" | 
 | #include "acpiphp.h" | 
 |  | 
 | static struct acpiphp_dock_station *ds; | 
 | #define MY_NAME "acpiphp_dock" | 
 |  | 
 |  | 
 | int is_dependent_device(acpi_handle handle) | 
 | { | 
 | 	return (get_dependent_device(handle) ? 1 : 0); | 
 | } | 
 |  | 
 |  | 
 | static acpi_status | 
 | find_dependent_device(acpi_handle handle, u32 lvl, void *context, void **rv) | 
 | { | 
 | 	int *count = (int *)context; | 
 |  | 
 | 	if (is_dependent_device(handle)) { | 
 | 		(*count)++; | 
 | 		return AE_CTRL_TERMINATE; | 
 | 	} else { | 
 | 		return AE_OK; | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 |  | 
 |  | 
 | void add_dependent_device(struct dependent_device *new_dd) | 
 | { | 
 | 	list_add_tail(&new_dd->device_list, &ds->dependent_devices); | 
 | } | 
 |  | 
 |  | 
 | void add_pci_dependent_device(struct dependent_device *new_dd) | 
 | { | 
 | 	list_add_tail(&new_dd->pci_list, &ds->pci_dependent_devices); | 
 | } | 
 |  | 
 |  | 
 |  | 
 | struct dependent_device * get_dependent_device(acpi_handle handle) | 
 | { | 
 | 	struct dependent_device *dd; | 
 |  | 
 | 	if (!ds) | 
 | 		return NULL; | 
 |  | 
 | 	list_for_each_entry(dd, &ds->dependent_devices, device_list) { | 
 | 		if (handle == dd->handle) | 
 | 			return dd; | 
 | 	} | 
 | 	return NULL; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | struct dependent_device *alloc_dependent_device(acpi_handle handle) | 
 | { | 
 | 	struct dependent_device *dd; | 
 |  | 
 | 	dd = kzalloc(sizeof(*dd), GFP_KERNEL); | 
 | 	if (dd) { | 
 | 		INIT_LIST_HEAD(&dd->pci_list); | 
 | 		INIT_LIST_HEAD(&dd->device_list); | 
 | 		dd->handle = handle; | 
 | 	} | 
 | 	return dd; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static int is_dock(acpi_handle handle) | 
 | { | 
 | 	acpi_status status; | 
 | 	acpi_handle tmp; | 
 |  | 
 | 	status = acpi_get_handle(handle, "_DCK", &tmp); | 
 | 	if (ACPI_FAILURE(status)) { | 
 | 		return 0; | 
 | 	} | 
 | 	return 1; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static int dock_present(void) | 
 | { | 
 | 	unsigned long sta; | 
 | 	acpi_status status; | 
 |  | 
 | 	if (ds) { | 
 | 		status = acpi_evaluate_integer(ds->handle, "_STA", NULL, &sta); | 
 | 		if (ACPI_SUCCESS(status) && sta) | 
 | 			return 1; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static void eject_dock(void) | 
 | { | 
 | 	struct acpi_object_list arg_list; | 
 | 	union acpi_object arg; | 
 |  | 
 | 	arg_list.count = 1; | 
 | 	arg_list.pointer = &arg; | 
 | 	arg.type = ACPI_TYPE_INTEGER; | 
 | 	arg.integer.value = 1; | 
 |  | 
 | 	if (ACPI_FAILURE(acpi_evaluate_object(ds->handle, "_EJ0", | 
 | 					&arg_list, NULL)) || dock_present()) | 
 | 		warn("%s: failed to eject dock!\n", __FUNCTION__); | 
 |  | 
 | 	return; | 
 | } | 
 |  | 
 |  | 
 |  | 
 |  | 
 | static acpi_status handle_dock(int dock) | 
 | { | 
 | 	acpi_status status; | 
 | 	struct acpi_object_list arg_list; | 
 | 	union acpi_object arg; | 
 | 	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; | 
 |  | 
 | 	dbg("%s: %s\n", __FUNCTION__, dock ? "docking" : "undocking"); | 
 |  | 
 | 	/* _DCK method has one argument */ | 
 | 	arg_list.count = 1; | 
 | 	arg_list.pointer = &arg; | 
 | 	arg.type = ACPI_TYPE_INTEGER; | 
 | 	arg.integer.value = dock; | 
 | 	status = acpi_evaluate_object(ds->handle, "_DCK", | 
 | 					&arg_list, &buffer); | 
 | 	if (ACPI_FAILURE(status)) | 
 | 		err("%s: failed to execute _DCK\n", __FUNCTION__); | 
 | 	acpi_os_free(buffer.pointer); | 
 |  | 
 | 	return status; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static inline void dock(void) | 
 | { | 
 | 	handle_dock(1); | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static inline void undock(void) | 
 | { | 
 | 	handle_dock(0); | 
 | } | 
 |  | 
 |  | 
 |  | 
 | /* | 
 |  * the _DCK method can do funny things... and sometimes not | 
 |  * hah-hah funny. | 
 |  * | 
 |  * TBD - figure out a way to only call fixups for | 
 |  * systems that require them. | 
 |  */ | 
 | static void post_dock_fixups(void) | 
 | { | 
 | 	struct pci_bus *bus; | 
 | 	u32 buses; | 
 | 	struct dependent_device *dd; | 
 |  | 
 | 	list_for_each_entry(dd, &ds->pci_dependent_devices, pci_list) { | 
 | 		bus = dd->func->slot->bridge->pci_bus; | 
 |  | 
 | 		/* fixup bad _DCK function that rewrites | 
 | 	 	 * secondary bridge on slot | 
 | 	 	 */ | 
 | 		pci_read_config_dword(bus->self, | 
 | 				PCI_PRIMARY_BUS, | 
 | 				&buses); | 
 |  | 
 | 		if (((buses >> 8) & 0xff) != bus->secondary) { | 
 | 			buses = (buses & 0xff000000) | 
 | 	     			| ((unsigned int)(bus->primary)     <<  0) | 
 | 	     			| ((unsigned int)(bus->secondary)   <<  8) | 
 | 	     			| ((unsigned int)(bus->subordinate) << 16); | 
 | 			pci_write_config_dword(bus->self, | 
 | 					PCI_PRIMARY_BUS, | 
 | 					buses); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static void hotplug_pci(u32 type) | 
 | { | 
 | 	struct dependent_device *dd; | 
 |  | 
 | 	list_for_each_entry(dd, &ds->pci_dependent_devices, pci_list) | 
 | 		handle_hotplug_event_func(dd->handle, type, dd->func); | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static inline void begin_dock(void) | 
 | { | 
 | 	ds->flags |= DOCK_DOCKING; | 
 | } | 
 |  | 
 |  | 
 | static inline void complete_dock(void) | 
 | { | 
 | 	ds->flags &= ~(DOCK_DOCKING); | 
 | 	ds->last_dock_time = jiffies; | 
 | } | 
 |  | 
 |  | 
 | static int dock_in_progress(void) | 
 | { | 
 | 	if (ds->flags & DOCK_DOCKING || | 
 | 		ds->last_dock_time == jiffies) { | 
 | 		dbg("dock in progress\n"); | 
 | 		return 1; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | static void | 
 | handle_hotplug_event_dock(acpi_handle handle, u32 type, void *context) | 
 | { | 
 | 	dbg("%s: enter\n", __FUNCTION__); | 
 |  | 
 | 	switch (type) { | 
 | 		case ACPI_NOTIFY_BUS_CHECK: | 
 | 			dbg("BUS Check\n"); | 
 | 			if (!dock_in_progress() && dock_present()) { | 
 | 				begin_dock(); | 
 | 				dock(); | 
 | 				if (!dock_present()) { | 
 | 					err("Unable to dock!\n"); | 
 | 					break; | 
 | 				} | 
 | 				post_dock_fixups(); | 
 | 				hotplug_pci(type); | 
 | 				complete_dock(); | 
 | 			} | 
 | 			break; | 
 | 		case ACPI_NOTIFY_EJECT_REQUEST: | 
 | 			dbg("EJECT request\n"); | 
 | 			if (!dock_in_progress() && dock_present()) { | 
 | 				hotplug_pci(type); | 
 | 				undock(); | 
 | 				eject_dock(); | 
 | 				if (dock_present()) | 
 | 					err("Unable to undock!\n"); | 
 | 			} | 
 | 			break; | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 |  | 
 |  | 
 | static acpi_status | 
 | find_dock_ejd(acpi_handle handle, u32 lvl, void *context, void **rv) | 
 | { | 
 | 	acpi_status status; | 
 | 	acpi_handle tmp; | 
 | 	acpi_handle dck_handle = (acpi_handle) context; | 
 | 	char objname[64]; | 
 | 	struct acpi_buffer buffer = { .length = sizeof(objname), | 
 | 				      .pointer = objname }; | 
 | 	struct acpi_buffer ejd_buffer = {ACPI_ALLOCATE_BUFFER, NULL}; | 
 | 	union acpi_object *ejd_obj; | 
 |  | 
 | 	status = acpi_get_handle(handle, "_EJD", &tmp); | 
 | 	if (ACPI_FAILURE(status)) | 
 | 		return AE_OK; | 
 |  | 
 | 	/* make sure we are dependent on the dock device, | 
 | 	 * by executing the _EJD method, then getting a handle | 
 | 	 * to the device referenced by that name.  If that | 
 | 	 * device handle is the same handle as the dock station | 
 | 	 * handle, then we are a device dependent on the dock station | 
 | 	 */ | 
 | 	acpi_get_name(dck_handle, ACPI_FULL_PATHNAME, &buffer); | 
 | 	status = acpi_evaluate_object(handle, "_EJD", NULL, &ejd_buffer); | 
 | 	if (ACPI_FAILURE(status)) { | 
 | 		err("Unable to execute _EJD!\n"); | 
 | 		goto find_ejd_out; | 
 | 	} | 
 | 	ejd_obj = ejd_buffer.pointer; | 
 | 	status = acpi_get_handle(NULL, ejd_obj->string.pointer, &tmp); | 
 | 	if (ACPI_FAILURE(status)) | 
 | 		goto find_ejd_out; | 
 |  | 
 | 	if (tmp == dck_handle) { | 
 | 		struct dependent_device *dd; | 
 | 		dbg("%s: found device dependent on dock\n", __FUNCTION__); | 
 | 		dd = alloc_dependent_device(handle); | 
 | 		if (!dd) { | 
 | 			err("Can't allocate memory for dependent device!\n"); | 
 | 			goto find_ejd_out; | 
 | 		} | 
 | 		add_dependent_device(dd); | 
 | 	} | 
 |  | 
 | find_ejd_out: | 
 | 	acpi_os_free(ejd_buffer.pointer); | 
 | 	return AE_OK; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | int detect_dependent_devices(acpi_handle *bridge_handle) | 
 | { | 
 | 	acpi_status status; | 
 | 	int count; | 
 |  | 
 | 	count = 0; | 
 |  | 
 | 	status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge_handle, | 
 | 					(u32)1, find_dependent_device, | 
 | 					(void *)&count, NULL); | 
 |  | 
 | 	return count; | 
 | } | 
 |  | 
 |  | 
 |  | 
 |  | 
 |  | 
 | static acpi_status | 
 | find_dock(acpi_handle handle, u32 lvl, void *context, void **rv) | 
 | { | 
 | 	int *count = (int *)context; | 
 |  | 
 | 	if (is_dock(handle)) { | 
 | 		dbg("%s: found dock\n", __FUNCTION__); | 
 | 		ds = kzalloc(sizeof(*ds), GFP_KERNEL); | 
 | 		ds->handle = handle; | 
 | 		INIT_LIST_HEAD(&ds->dependent_devices); | 
 | 		INIT_LIST_HEAD(&ds->pci_dependent_devices); | 
 |  | 
 | 		/* look for devices dependent on dock station */ | 
 | 		acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, | 
 | 			ACPI_UINT32_MAX, find_dock_ejd, handle, NULL); | 
 |  | 
 | 		acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, | 
 | 			handle_hotplug_event_dock, ds); | 
 | 		(*count)++; | 
 | 	} | 
 |  | 
 | 	return AE_OK; | 
 | } | 
 |  | 
 |  | 
 |  | 
 |  | 
 | int find_dock_station(void) | 
 | { | 
 | 	int num = 0; | 
 |  | 
 | 	ds = NULL; | 
 |  | 
 | 	/* start from the root object, because some laptops define | 
 | 	 * _DCK methods outside the scope of PCI (IBM x-series laptop) | 
 | 	 */ | 
 | 	acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, | 
 | 			ACPI_UINT32_MAX, find_dock, &num, NULL); | 
 |  | 
 | 	return num; | 
 | } | 
 |  | 
 |  | 
 |  | 
 | void remove_dock_station(void) | 
 | { | 
 | 	struct dependent_device *dd, *tmp; | 
 | 	if (ds) { | 
 | 		if (ACPI_FAILURE(acpi_remove_notify_handler(ds->handle, | 
 | 			ACPI_SYSTEM_NOTIFY, handle_hotplug_event_dock))) | 
 | 			err("failed to remove dock notify handler\n"); | 
 |  | 
 | 		/* free all dependent devices */ | 
 | 		list_for_each_entry_safe(dd, tmp, &ds->dependent_devices, | 
 | 				device_list) | 
 | 			kfree(dd); | 
 |  | 
 | 		/* no need to touch the pci_dependent_device list, | 
 | 		 * cause all memory was freed above | 
 | 		 */ | 
 | 		kfree(ds); | 
 | 	} | 
 | } | 
 |  | 
 |  |