brintos

brintos / linux-shallow public Read only

0
0
Text · 7.6 KiB · 7c5b040 Raw
326 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *  acpi_ac.c - ACPI AC Adapter Driver (Revision: 27)4 *5 *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>6 *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>7 */8 9#define pr_fmt(fmt) "ACPI: AC: " fmt10 11#include <linux/kernel.h>12#include <linux/module.h>13#include <linux/slab.h>14#include <linux/init.h>15#include <linux/types.h>16#include <linux/dmi.h>17#include <linux/delay.h>18#include <linux/platform_device.h>19#include <linux/power_supply.h>20#include <linux/string_choices.h>21#include <linux/acpi.h>22#include <acpi/battery.h>23 24#define ACPI_AC_CLASS			"ac_adapter"25#define ACPI_AC_DEVICE_NAME		"AC Adapter"26#define ACPI_AC_FILE_STATE		"state"27#define ACPI_AC_NOTIFY_STATUS		0x8028#define ACPI_AC_STATUS_OFFLINE		0x0029#define ACPI_AC_STATUS_ONLINE		0x0130#define ACPI_AC_STATUS_UNKNOWN		0xFF31 32MODULE_AUTHOR("Paul Diefenbaugh");33MODULE_DESCRIPTION("ACPI AC Adapter Driver");34MODULE_LICENSE("GPL");35 36static int acpi_ac_probe(struct platform_device *pdev);37static void acpi_ac_remove(struct platform_device *pdev);38 39static void acpi_ac_notify(acpi_handle handle, u32 event, void *data);40 41static const struct acpi_device_id ac_device_ids[] = {42	{"ACPI0003", 0},43	{"", 0},44};45MODULE_DEVICE_TABLE(acpi, ac_device_ids);46 47#ifdef CONFIG_PM_SLEEP48static int acpi_ac_resume(struct device *dev);49#endif50static SIMPLE_DEV_PM_OPS(acpi_ac_pm, NULL, acpi_ac_resume);51 52static int ac_sleep_before_get_state_ms;53static int ac_only;54 55struct acpi_ac {56	struct power_supply *charger;57	struct power_supply_desc charger_desc;58	struct acpi_device *device;59	unsigned long long state;60	struct notifier_block battery_nb;61};62 63#define to_acpi_ac(x) power_supply_get_drvdata(x)64 65/* AC Adapter Management */66static int acpi_ac_get_state(struct acpi_ac *ac)67{68	acpi_status status = AE_OK;69 70	if (!ac)71		return -EINVAL;72 73	if (ac_only) {74		ac->state = 1;75		return 0;76	}77 78	status = acpi_evaluate_integer(ac->device->handle, "_PSR", NULL,79				       &ac->state);80	if (ACPI_FAILURE(status)) {81		acpi_handle_info(ac->device->handle,82				"Error reading AC Adapter state: %s\n",83				acpi_format_exception(status));84		ac->state = ACPI_AC_STATUS_UNKNOWN;85		return -ENODEV;86	}87 88	return 0;89}90 91/* sysfs I/F */92static int get_ac_property(struct power_supply *psy,93			   enum power_supply_property psp,94			   union power_supply_propval *val)95{96	struct acpi_ac *ac = to_acpi_ac(psy);97 98	if (!ac)99		return -ENODEV;100 101	if (acpi_ac_get_state(ac))102		return -ENODEV;103 104	switch (psp) {105	case POWER_SUPPLY_PROP_ONLINE:106		val->intval = ac->state;107		break;108	default:109		return -EINVAL;110	}111 112	return 0;113}114 115static const enum power_supply_property ac_props[] = {116	POWER_SUPPLY_PROP_ONLINE,117};118 119/* Driver Model */120static void acpi_ac_notify(acpi_handle handle, u32 event, void *data)121{122	struct acpi_ac *ac = data;123	struct acpi_device *adev = ac->device;124 125	switch (event) {126	default:127		acpi_handle_debug(adev->handle, "Unsupported event [0x%x]\n",128				  event);129		fallthrough;130	case ACPI_AC_NOTIFY_STATUS:131	case ACPI_NOTIFY_BUS_CHECK:132	case ACPI_NOTIFY_DEVICE_CHECK:133		/*134		 * A buggy BIOS may notify AC first and then sleep for135		 * a specific time before doing actual operations in the136		 * EC event handler (_Qxx). This will cause the AC state137		 * reported by the ACPI event to be incorrect, so wait for a138		 * specific time for the EC event handler to make progress.139		 */140		if (ac_sleep_before_get_state_ms > 0)141			msleep(ac_sleep_before_get_state_ms);142 143		acpi_ac_get_state(ac);144		acpi_bus_generate_netlink_event(adev->pnp.device_class,145						  dev_name(&adev->dev), event,146						  (u32) ac->state);147		acpi_notifier_call_chain(adev, event, (u32) ac->state);148		power_supply_changed(ac->charger);149	}150}151 152static int acpi_ac_battery_notify(struct notifier_block *nb,153				  unsigned long action, void *data)154{155	struct acpi_ac *ac = container_of(nb, struct acpi_ac, battery_nb);156	struct acpi_bus_event *event = (struct acpi_bus_event *)data;157 158	/*159	 * On HP Pavilion dv6-6179er AC status notifications aren't triggered160	 * when adapter is plugged/unplugged. However, battery status161	 * notifications are triggered when battery starts charging or162	 * discharging. Re-reading AC status triggers lost AC notifications,163	 * if AC status has changed.164	 */165	if (strcmp(event->device_class, ACPI_BATTERY_CLASS) == 0 &&166	    event->type == ACPI_BATTERY_NOTIFY_STATUS)167		acpi_ac_get_state(ac);168 169	return NOTIFY_OK;170}171 172static int __init thinkpad_e530_quirk(const struct dmi_system_id *d)173{174	ac_sleep_before_get_state_ms = 1000;175	return 0;176}177 178static int __init ac_only_quirk(const struct dmi_system_id *d)179{180	ac_only = 1;181	return 0;182}183 184/* Please keep this list alphabetically sorted */185static const struct dmi_system_id ac_dmi_table[]  __initconst = {186	{187		/* Kodlix GK45 returning incorrect state */188		.callback = ac_only_quirk,189		.matches = {190			DMI_MATCH(DMI_PRODUCT_NAME, "GK45"),191		},192	},193	{194		/* Lenovo Thinkpad e530, see comment in acpi_ac_notify() */195		.callback = thinkpad_e530_quirk,196		.matches = {197			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),198			DMI_MATCH(DMI_PRODUCT_NAME, "32597CG"),199		},200	},201	{},202};203 204static int acpi_ac_probe(struct platform_device *pdev)205{206	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);207	struct power_supply_config psy_cfg = {};208	struct acpi_ac *ac;209	int result;210 211	ac = kzalloc(sizeof(struct acpi_ac), GFP_KERNEL);212	if (!ac)213		return -ENOMEM;214 215	ac->device = adev;216	strscpy(acpi_device_name(adev), ACPI_AC_DEVICE_NAME);217	strscpy(acpi_device_class(adev), ACPI_AC_CLASS);218 219	platform_set_drvdata(pdev, ac);220 221	result = acpi_ac_get_state(ac);222	if (result)223		goto err_release_ac;224 225	psy_cfg.drv_data = ac;226 227	ac->charger_desc.name = acpi_device_bid(adev);228	ac->charger_desc.type = POWER_SUPPLY_TYPE_MAINS;229	ac->charger_desc.properties = ac_props;230	ac->charger_desc.num_properties = ARRAY_SIZE(ac_props);231	ac->charger_desc.get_property = get_ac_property;232	ac->charger = power_supply_register(&pdev->dev,233					    &ac->charger_desc, &psy_cfg);234	if (IS_ERR(ac->charger)) {235		result = PTR_ERR(ac->charger);236		goto err_release_ac;237	}238 239	pr_info("%s [%s] (%s-line)\n", acpi_device_name(adev),240		acpi_device_bid(adev), str_on_off(ac->state));241 242	ac->battery_nb.notifier_call = acpi_ac_battery_notify;243	register_acpi_notifier(&ac->battery_nb);244 245	result = acpi_dev_install_notify_handler(adev, ACPI_ALL_NOTIFY,246						 acpi_ac_notify, ac);247	if (result)248		goto err_unregister;249 250	return 0;251 252err_unregister:253	power_supply_unregister(ac->charger);254	unregister_acpi_notifier(&ac->battery_nb);255err_release_ac:256	kfree(ac);257 258	return result;259}260 261#ifdef CONFIG_PM_SLEEP262static int acpi_ac_resume(struct device *dev)263{264	struct acpi_ac *ac = dev_get_drvdata(dev);265	unsigned int old_state;266 267	old_state = ac->state;268	if (acpi_ac_get_state(ac))269		return 0;270	if (old_state != ac->state)271		power_supply_changed(ac->charger);272 273	return 0;274}275#else276#define acpi_ac_resume NULL277#endif278 279static void acpi_ac_remove(struct platform_device *pdev)280{281	struct acpi_ac *ac = platform_get_drvdata(pdev);282 283	acpi_dev_remove_notify_handler(ac->device, ACPI_ALL_NOTIFY,284				       acpi_ac_notify);285	power_supply_unregister(ac->charger);286	unregister_acpi_notifier(&ac->battery_nb);287 288	kfree(ac);289}290 291static struct platform_driver acpi_ac_driver = {292	.probe = acpi_ac_probe,293	.remove_new = acpi_ac_remove,294	.driver = {295		.name = "ac",296		.acpi_match_table = ac_device_ids,297		.pm = &acpi_ac_pm,298	},299};300 301static int __init acpi_ac_init(void)302{303	int result;304 305	if (acpi_disabled)306		return -ENODEV;307 308	if (acpi_quirk_skip_acpi_ac_and_battery())309		return -ENODEV;310 311	dmi_check_system(ac_dmi_table);312 313	result = platform_driver_register(&acpi_ac_driver);314	if (result < 0)315		return -ENODEV;316 317	return 0;318}319 320static void __exit acpi_ac_exit(void)321{322	platform_driver_unregister(&acpi_ac_driver);323}324module_init(acpi_ac_init);325module_exit(acpi_ac_exit);326