summaryrefslogtreecommitdiffstats
path: root/drivers/mfd/cros_ec_acpi_gpe.c
blob: 56d305dab2d487f7b07b8c9f5424320823aea03a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/*
 * ChromeOS EC multi-function device
 *
 * Copyright (C) 2017 Google, Inc
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * The ChromeOS EC multi function device is used to mux all the requests
 * to the EC device for its multiple features: keyboard controller,
 * battery charging and regulator control, firmware update.
 */
#include <linux/acpi.h>

#define ACPI_LID_DEVICE      "LID0"

static int ec_wake_gpe = -EINVAL;

/*
 * This handler indicates to ACPI core that this GPE should stay enabled for
 * lid to work in suspend to idle path.
 */
static u32 cros_ec_gpe_handler(acpi_handle gpe_device, u32 gpe_number,
			       void *data)
{
	return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE;
}

/*
 * Get ACPI GPE for LID0 device.
 */
static int cros_ec_get_ec_wake_gpe(struct device *dev)
{
	struct acpi_device *cros_acpi_dev;
	struct acpi_device *adev;
	acpi_handle handle;
	acpi_status status;
	int ret;

	cros_acpi_dev = ACPI_COMPANION(dev);

	if (!cros_acpi_dev || !cros_acpi_dev->parent ||
	   !cros_acpi_dev->parent->handle)
		return -EINVAL;

	status = acpi_get_handle(cros_acpi_dev->parent->handle, ACPI_LID_DEVICE,
				 &handle);
	if (ACPI_FAILURE(status))
		return -EINVAL;

	ret = acpi_bus_get_device(handle, &adev);
	if (ret)
		return ret;

	return adev->wakeup.gpe_number;
}

int cros_ec_acpi_install_gpe_handler(struct device *dev)
{
	acpi_status status;

	ec_wake_gpe = cros_ec_get_ec_wake_gpe(dev);

	if (ec_wake_gpe < 0)
		return ec_wake_gpe;

	status = acpi_install_gpe_handler(NULL, ec_wake_gpe,
					  ACPI_GPE_EDGE_TRIGGERED,
					  &cros_ec_gpe_handler, NULL);
	if (ACPI_FAILURE(status))
		return -ENODEV;

	dev_info(dev, "Initialized, GPE = 0x%x\n", ec_wake_gpe);

	return 0;
}

void cros_ec_acpi_remove_gpe_handler(void)
{
	acpi_status status;

	if (ec_wake_gpe < 0)
		return;

	status = acpi_remove_gpe_handler(NULL, ec_wake_gpe,
						 &cros_ec_gpe_handler);
	if (ACPI_FAILURE(status))
		pr_err("failed to remove gpe handler\n");
}

void cros_ec_acpi_clear_gpe(void)
{
	if (ec_wake_gpe < 0)
		return;

	acpi_clear_gpe(NULL, ec_wake_gpe);
}