1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <ddk/binding.h>
6 #include <ddk/debug.h>
7 #include <ddk/device.h>
8 #include <ddk/driver.h>
9
10 #include <acpica/acpi.h>
11 #include <fuchsia/hardware/power/c/fidl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <threads.h>
15 #include <zircon/syscalls.h>
16 #include <zircon/types.h>
17
18 #include "dev.h"
19 #include "errors.h"
20 #include "power.h"
21
22 typedef struct acpi_pwrsrc_device {
23 zx_device_t* zxdev;
24
25 ACPI_HANDLE acpi_handle;
26
27 // event to notify on
28 zx_handle_t event;
29
30 power_info_t info;
31
32 mtx_t lock;
33 } acpi_pwrsrc_device_t;
34
call_PSR(acpi_pwrsrc_device_t * dev,bool notify)35 static zx_status_t call_PSR(acpi_pwrsrc_device_t* dev, bool notify) {
36 ACPI_OBJECT obj = {
37 .Type = ACPI_TYPE_INTEGER,
38 };
39 ACPI_BUFFER buffer = {
40 .Length = sizeof(obj),
41 .Pointer = &obj,
42 };
43 ACPI_STATUS acpi_status = AcpiEvaluateObject(dev->acpi_handle, (char*)"_PSR", NULL, &buffer);
44 if (acpi_status == AE_OK) {
45 mtx_lock(&dev->lock);
46 uint32_t state = dev->info.state;
47 if (obj.Integer.Value) {
48 dev->info.state |= POWER_STATE_ONLINE;
49 } else {
50 dev->info.state &= ~POWER_STATE_ONLINE;
51 }
52 zxlogf(DEBUG1, "acpi-pwrsrc: 0x%x -> 0x%x\n", state, dev->info.state);
53 if (notify && (state != dev->info.state)) {
54 zx_object_signal(dev->event, 0, ZX_USER_SIGNAL_0);
55 }
56 mtx_unlock(&dev->lock);
57 }
58 return acpi_to_zx_status(acpi_status);
59 }
60
acpi_pwrsrc_notify(ACPI_HANDLE handle,UINT32 value,void * ctx)61 static void acpi_pwrsrc_notify(ACPI_HANDLE handle, UINT32 value, void* ctx) {
62 acpi_pwrsrc_device_t* dev = ctx;
63 zxlogf(TRACE, "acpi-pwrsrc: got event 0x%x\n", value);
64 call_PSR(dev, true);
65 }
66
acpi_pwrsrc_release(void * ctx)67 static void acpi_pwrsrc_release(void* ctx) {
68 acpi_pwrsrc_device_t* dev = ctx;
69 AcpiRemoveNotifyHandler(dev->acpi_handle, ACPI_DEVICE_NOTIFY, acpi_pwrsrc_notify);
70 if (dev->event != ZX_HANDLE_INVALID) {
71 zx_handle_close(dev->event);
72 }
73 free(dev);
74 }
75
fidl_pwrsrc_get_power_info(void * ctx,fidl_txn_t * txn)76 zx_status_t fidl_pwrsrc_get_power_info(void* ctx, fidl_txn_t* txn) {
77 acpi_pwrsrc_device_t* dev = ctx;
78 struct fuchsia_hardware_power_SourceInfo info;
79
80 mtx_lock(&dev->lock);
81 info.state = dev->info.state;
82 info.type = dev->info.type;
83 mtx_unlock(&dev->lock);
84
85 // reading state clears the signal
86 zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0);
87 return fuchsia_hardware_power_SourceGetPowerInfo_reply(txn, ZX_OK, &info);
88 }
89
fidl_pwrsrc_get_state_change_event(void * ctx,fidl_txn_t * txn)90 zx_status_t fidl_pwrsrc_get_state_change_event(void* ctx, fidl_txn_t* txn) {
91 acpi_pwrsrc_device_t* dev = ctx;
92 zx_handle_t out_handle;
93 zx_rights_t rights = ZX_RIGHT_WAIT | ZX_RIGHT_TRANSFER;
94 zx_status_t status = zx_handle_duplicate(dev->event, rights, &out_handle);
95
96 if (status == ZX_OK) {
97 // clear the signal before returning
98 zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0);
99 }
100 return fuchsia_hardware_power_SourceGetStateChangeEvent_reply(txn, status, out_handle);
101 }
102
103 static fuchsia_hardware_power_Source_ops_t fidl_ops = {
104 .GetPowerInfo = fidl_pwrsrc_get_power_info,
105 .GetStateChangeEvent = fidl_pwrsrc_get_state_change_event,
106 };
107
fuchsia_hardware_power_message_instance(void * ctx,fidl_msg_t * msg,fidl_txn_t * txn)108 static zx_status_t fuchsia_hardware_power_message_instance(void* ctx, fidl_msg_t* msg,
109 fidl_txn_t* txn) {
110 return fuchsia_hardware_power_Source_dispatch(ctx, txn, msg, &fidl_ops);
111 }
112
113 static zx_protocol_device_t acpi_pwrsrc_device_proto = {
114 .version = DEVICE_OPS_VERSION,
115 .message = fuchsia_hardware_power_message_instance,
116 .release = acpi_pwrsrc_release,
117 };
118
pwrsrc_init(zx_device_t * parent,ACPI_HANDLE acpi_handle)119 zx_status_t pwrsrc_init(zx_device_t* parent, ACPI_HANDLE acpi_handle) {
120 acpi_pwrsrc_device_t* dev = calloc(1, sizeof(acpi_pwrsrc_device_t));
121 if (!dev) {
122 return ZX_ERR_NO_MEMORY;
123 }
124
125 dev->acpi_handle = acpi_handle;
126 mtx_init(&dev->lock, mtx_plain);
127
128 zx_status_t status = zx_event_create(0, &dev->event);
129 if (status != ZX_OK) {
130 free(dev);
131 return status;
132 }
133
134 dev->info.type = POWER_TYPE_AC;
135 call_PSR(dev, false);
136
137 ACPI_STATUS acpi_status = AcpiInstallNotifyHandler(acpi_handle, ACPI_DEVICE_NOTIFY,
138 acpi_pwrsrc_notify, dev);
139 if (acpi_status != AE_OK) {
140 zxlogf(ERROR, "acpi-pwrsrc: could not install notify handler\n");
141 acpi_pwrsrc_release(dev);
142 return acpi_to_zx_status(acpi_status);
143 }
144
145 // read initial value
146 acpi_pwrsrc_notify(acpi_handle, 0, dev);
147
148 device_add_args_t args = {
149 .version = DEVICE_ADD_ARGS_VERSION,
150 .name = "acpi-pwrsrc",
151 .ctx = dev,
152 .ops = &acpi_pwrsrc_device_proto,
153 .proto_id = ZX_PROTOCOL_POWER,
154 };
155
156 status = device_add(parent, &args, &dev->zxdev);
157 if (status != ZX_OK) {
158 zxlogf(ERROR, "acpi-pwrsrc: could not add device! err=%d\n", status);
159 acpi_pwrsrc_release(dev);
160 return status;
161 }
162
163 zxlogf(TRACE, "acpi-pwrsrc: initialized\n");
164
165 return ZX_OK;
166 }
167