| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/binding.h> |
| |
| #include <zircon/types.h> |
| #include <zircon/syscalls.h> |
| #include <fdio/debug.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <threads.h> |
| |
| #include <acpica/acpi.h> |
| #include <zircon/device/power.h> |
| |
| #include "dev.h" |
| #include "errors.h" |
| |
| #define MXDEBUG 0 |
| |
| typedef struct acpi_pwrsrc_device { |
| zx_device_t* mxdev; |
| |
| ACPI_HANDLE acpi_handle; |
| |
| // event to notify on |
| zx_handle_t event; |
| |
| power_info_t info; |
| |
| mtx_t lock; |
| } acpi_pwrsrc_device_t; |
| |
| static zx_status_t call_PSR(acpi_pwrsrc_device_t* dev, bool notify) { |
| ACPI_OBJECT obj = { |
| .Type = ACPI_TYPE_INTEGER, |
| }; |
| ACPI_BUFFER buffer = { |
| .Length = sizeof(obj), |
| .Pointer = &obj, |
| }; |
| ACPI_STATUS acpi_status = AcpiEvaluateObject(dev->acpi_handle, (char*)"_PSR", NULL, &buffer); |
| if (acpi_status == AE_OK) { |
| mtx_lock(&dev->lock); |
| uint32_t state = dev->info.state; |
| if (obj.Integer.Value) { |
| dev->info.state |= POWER_STATE_ONLINE; |
| } else { |
| dev->info.state &= ~POWER_STATE_ONLINE; |
| } |
| if (notify && (state != dev->info.state)) { |
| zx_object_signal(dev->event, 0, ZX_USER_SIGNAL_0); |
| } |
| mtx_unlock(&dev->lock); |
| } |
| return acpi_to_zx_status(acpi_status); |
| } |
| |
| static void acpi_pwrsrc_notify(ACPI_HANDLE handle, UINT32 value, void* ctx) { |
| acpi_pwrsrc_device_t* dev = ctx; |
| xprintf("acpi-pwrsrc: got event 0x%x\n", value); |
| call_PSR(dev, true); |
| } |
| |
| static zx_status_t acpi_pwrsrc_ioctl(void* ctx, uint32_t op, |
| const void* in_buf, size_t in_len, |
| void* out_buf, size_t out_len, size_t* out_actual) { |
| acpi_pwrsrc_device_t* dev = ctx; |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| switch (op) { |
| case IOCTL_POWER_GET_INFO: { |
| if (out_len != sizeof(power_info_t)) { |
| status = ZX_ERR_INVALID_ARGS; |
| goto err; |
| } |
| |
| // reading state clears the signal |
| zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0); |
| |
| power_info_t* info = (power_info_t*)out_buf; |
| mtx_lock(&dev->lock); |
| memcpy(info, &dev->info, sizeof(power_info_t)); |
| mtx_unlock(&dev->lock); |
| *out_actual = sizeof(power_info_t); |
| return ZX_OK; |
| } |
| case IOCTL_POWER_GET_STATE_CHANGE_EVENT: { |
| if (out_len != sizeof(zx_handle_t)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx_handle_t* out = (zx_handle_t*)out_buf; |
| zx_status_t status = zx_handle_duplicate(dev->event, |
| ZX_RIGHT_READ | ZX_RIGHT_TRANSFER, |
| out); |
| if (status != ZX_OK) { |
| goto err; |
| } |
| // clear the signal before returning |
| zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0); |
| *out_actual = sizeof(zx_handle_t); |
| return ZX_OK; |
| } |
| } |
| err: |
| *out_actual = 0; |
| return status; |
| } |
| |
| static void acpi_pwrsrc_release(void* ctx) { |
| acpi_pwrsrc_device_t* dev = ctx; |
| AcpiRemoveNotifyHandler(dev->acpi_handle, ACPI_DEVICE_NOTIFY, acpi_pwrsrc_notify); |
| if (dev->event != ZX_HANDLE_INVALID) { |
| zx_handle_close(dev->event); |
| } |
| free(dev); |
| } |
| |
| static zx_protocol_device_t acpi_pwrsrc_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .ioctl = acpi_pwrsrc_ioctl, |
| .release = acpi_pwrsrc_release, |
| }; |
| |
| zx_status_t pwrsrc_init(zx_device_t* parent, ACPI_HANDLE acpi_handle) { |
| acpi_pwrsrc_device_t* dev = calloc(1, sizeof(acpi_pwrsrc_device_t)); |
| if (!dev) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| dev->acpi_handle = acpi_handle; |
| mtx_init(&dev->lock, mtx_plain); |
| |
| zx_status_t status = zx_event_create(0, &dev->event); |
| if (status != ZX_OK) { |
| free(dev); |
| return status; |
| } |
| |
| dev->info.type = POWER_TYPE_AC; |
| call_PSR(dev, false); |
| |
| ACPI_STATUS acpi_status = AcpiInstallNotifyHandler(acpi_handle, ACPI_DEVICE_NOTIFY, |
| acpi_pwrsrc_notify, dev); |
| if (acpi_status != AE_OK) { |
| xprintf("acpi-pwrsrc: could not install notify handler\n"); |
| acpi_pwrsrc_release(dev); |
| return acpi_to_zx_status(acpi_status); |
| } |
| |
| // read initial value |
| acpi_pwrsrc_notify(acpi_handle, 0, dev); |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "acpi-pwrsrc", |
| .ctx = dev, |
| .ops = &acpi_pwrsrc_device_proto, |
| .proto_id = ZX_PROTOCOL_POWER, |
| }; |
| |
| status = device_add(parent, &args, &dev->mxdev); |
| if (status != ZX_OK) { |
| xprintf("acpi-pwrsrc: could not add device! err=%d\n", status); |
| acpi_pwrsrc_release(dev); |
| return status; |
| } |
| |
| xprintf("acpi-pwrsrc: initialized\n"); |
| |
| return ZX_OK; |
| } |