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/device.h>
6 #include <ddk/driver.h>
7 #include <ddk/binding.h>
8 #include <ddk/debug.h>
9
10 #include <zircon/types.h>
11 #include <zircon/device/thermal.h>
12 #include <inttypes.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15
16 #include <acpica/acpi.h>
17
18 #include "dev.h"
19 #include "errors.h"
20 #include "methods.h"
21 #include "util.h"
22
23 #define INT3403_TYPE_SENSOR 0x03
24 #define INT3403_THERMAL_EVENT 0x90
25
26 typedef struct acpi_thermal_device {
27 zx_device_t* zxdev;
28 ACPI_HANDLE acpi_handle;
29
30 mtx_t lock;
31
32 // event to notify on
33 zx_handle_t event;
34
35 // programmable trip points
36 uint32_t trip_point_count;
37 uint32_t trip_points[9];
38 } acpi_thermal_device_t;
39
acpi_thermal_get_info(acpi_thermal_device_t * dev,thermal_info_t * info)40 static zx_status_t acpi_thermal_get_info(acpi_thermal_device_t* dev, thermal_info_t* info) {
41 mtx_lock(&dev->lock);
42 zx_status_t st = ZX_OK;
43
44 uint64_t temp;
45 st = acpi_psv_call(dev->acpi_handle, &temp);
46 if (st != ZX_OK) {
47 goto out;
48 }
49 info->passive_temp = (uint32_t)temp; // we probably won't exceed 429496456.35 C
50 st = acpi_crt_call(dev->acpi_handle, &temp);
51 if (st != ZX_OK) {
52 goto out;
53 }
54 info->critical_temp = (uint32_t)temp;
55
56 info->max_trip_count = dev->trip_point_count;
57 memcpy(info->active_trip, dev->trip_points, sizeof(info->active_trip));
58
59 st = acpi_tmp_call(dev->acpi_handle, &temp);
60 if (st != ZX_OK) {
61 goto out;
62 }
63 info->state = 0;
64 if (info->active_trip[0] && (temp > info->active_trip[0])) {
65 info->state |= THERMAL_STATE_TRIP_VIOLATION;
66 }
67 out:
68 mtx_unlock(&dev->lock);
69 return st;
70 }
71
acpi_thermal_read(void * ctx,void * buf,size_t count,zx_off_t off,size_t * actual)72 static zx_status_t acpi_thermal_read(void* ctx, void* buf, size_t count, zx_off_t off,
73 size_t* actual) {
74 acpi_thermal_device_t* dev = ctx;
75 uint64_t v;
76
77 if (count < sizeof(uint32_t)) {
78 return ZX_ERR_BUFFER_TOO_SMALL;
79 }
80
81 zx_status_t status = acpi_tmp_call(dev->acpi_handle, &v);
82 if (status != ZX_OK) {
83 zxlogf(ERROR, "acpi-thermal: acpi error %d in _TMP\n", status);
84 return status;
85 }
86 uint32_t temp = (uint32_t)v;
87 memcpy(buf, &temp, sizeof(temp));
88 *actual = sizeof(temp);
89
90 return ZX_OK;
91 }
92
acpi_thermal_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)93 static zx_status_t acpi_thermal_ioctl(void* ctx, uint32_t op,
94 const void* in_buf, size_t in_len,
95 void* out_buf, size_t out_len, size_t* out_actual) {
96 acpi_thermal_device_t* dev = ctx;
97 switch (op) {
98 case IOCTL_THERMAL_GET_INFO: {
99 if (out_len != sizeof(thermal_info_t)) {
100 return ZX_ERR_INVALID_ARGS;
101 }
102
103 // reading state clears the signal
104 zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0);
105
106 thermal_info_t info;
107 zx_status_t status = acpi_thermal_get_info(dev, &info);
108 if (status != ZX_OK) {
109 return status;
110 }
111 memcpy(out_buf, &info, sizeof(info));
112 *out_actual = sizeof(info);
113 return ZX_OK;
114 }
115 case IOCTL_THERMAL_SET_TRIP: {
116 if (in_len != sizeof(trip_point_t)) {
117 return ZX_ERR_INVALID_ARGS;
118 }
119 if (dev->trip_point_count < 1) {
120 return ZX_ERR_NOT_SUPPORTED;
121 }
122 trip_point_t* tp = (trip_point_t*)in_buf;
123 // only one trip point for now
124 if (tp->id != 0) {
125 return ZX_ERR_INVALID_ARGS;
126 }
127 ACPI_STATUS acpi_status = acpi_evaluate_method_intarg(dev->acpi_handle,
128 "PAT0", tp->temp);
129 if (acpi_status != AE_OK) {
130 zxlogf(ERROR, "acpi-thermal: acpi error %d in PAT0\n", acpi_status);
131 return acpi_to_zx_status(acpi_status);
132 }
133 mtx_lock(&dev->lock);
134 dev->trip_points[0] = tp->temp;
135 mtx_unlock(&dev->lock);
136 return ZX_OK;
137 }
138 case IOCTL_THERMAL_GET_STATE_CHANGE_EVENT: {
139 if (out_len != sizeof(zx_handle_t)) {
140 return ZX_ERR_INVALID_ARGS;
141 }
142 zx_handle_t* out = (zx_handle_t*)out_buf;
143 zx_status_t status = zx_handle_duplicate(dev->event, ZX_RIGHT_SAME_RIGHTS, out);
144 if (status != ZX_OK) {
145 return status;
146 }
147
148 // clear the signal before returning
149 zx_object_signal(dev->event, ZX_USER_SIGNAL_0, 0);
150 *out_actual = sizeof(zx_handle_t);
151 return ZX_OK;
152 }
153 default:
154 return ZX_ERR_NOT_SUPPORTED;
155 }
156 }
157
acpi_thermal_notify(ACPI_HANDLE handle,UINT32 value,void * ctx)158 static void acpi_thermal_notify(ACPI_HANDLE handle, UINT32 value, void* ctx) {
159 acpi_thermal_device_t* dev = ctx;
160 zxlogf(TRACE, "acpi-thermal: got event 0x%x\n", value);
161 switch (value) {
162 case INT3403_THERMAL_EVENT:
163 zx_object_signal(dev->event, 0, ZX_USER_SIGNAL_0);
164 break;
165 }
166 }
167
acpi_thermal_release(void * ctx)168 static void acpi_thermal_release(void* ctx) {
169 acpi_thermal_device_t* dev = ctx;
170 AcpiRemoveNotifyHandler(dev->acpi_handle, ACPI_DEVICE_NOTIFY, acpi_thermal_notify);
171 zx_handle_close(dev->event);
172 free(dev);
173 }
174
175 static zx_protocol_device_t acpi_thermal_device_proto = {
176 .version = DEVICE_OPS_VERSION,
177 .read = acpi_thermal_read,
178 .ioctl = acpi_thermal_ioctl,
179 .release = acpi_thermal_release,
180 };
181
thermal_init(zx_device_t * parent,ACPI_DEVICE_INFO * info,ACPI_HANDLE acpi_handle)182 zx_status_t thermal_init(zx_device_t* parent, ACPI_DEVICE_INFO* info, ACPI_HANDLE acpi_handle) {
183 // only support sensors
184 uint64_t type;
185 ACPI_STATUS acpi_status = acpi_evaluate_integer(acpi_handle, "PTYP", &type);
186 if (acpi_status != AE_OK) {
187 zxlogf(ERROR, "acpi-thermal: acpi error %d in PTYP\n", acpi_status);
188 return acpi_to_zx_status(acpi_status);
189 }
190 if (type != INT3403_TYPE_SENSOR) {
191 return ZX_ERR_NOT_SUPPORTED;
192 }
193
194 acpi_thermal_device_t* dev = calloc(1, sizeof(acpi_thermal_device_t));
195 if (!dev) {
196 return ZX_ERR_NO_MEMORY;
197 }
198
199 dev->acpi_handle = acpi_handle;
200
201 zx_status_t status = zx_event_create(0, &dev->event);
202 if (status != ZX_OK) {
203 zxlogf(ERROR, "acpi-thermal: error %d in zx_event_create\n", status);
204 acpi_thermal_release(dev);
205 return status;
206 }
207
208 // install acpi event handler
209 acpi_status = AcpiInstallNotifyHandler(acpi_handle, ACPI_DEVICE_NOTIFY,
210 acpi_thermal_notify, dev);
211 if (acpi_status != AE_OK) {
212 zxlogf(ERROR, "acpi-thermal: could not install notify handler\n");
213 acpi_thermal_release(dev);
214 return acpi_to_zx_status(acpi_status);
215 }
216
217 uint64_t v;
218 acpi_status = acpi_evaluate_integer(dev->acpi_handle, "PATC", &v);
219 if (acpi_status != AE_OK) {
220 zxlogf(ERROR, "acpi-thermal: could not get auxiliary trip count\n");
221 return acpi_status;
222 }
223 dev->trip_point_count = (uint32_t)v;
224
225 char name[5];
226 memcpy(name, &info->Name, sizeof(UINT32));
227 name[4] = '\0';
228
229 device_add_args_t args = {
230 .version = DEVICE_ADD_ARGS_VERSION,
231 .name = name,
232 .ctx = dev,
233 .ops = &acpi_thermal_device_proto,
234 .proto_id = ZX_PROTOCOL_THERMAL,
235 };
236
237 status = device_add(parent, &args, &dev->zxdev);
238 if (status != ZX_OK) {
239 zxlogf(ERROR, "acpi-thermal: could not add device! err=%d\n", status);
240 acpi_thermal_release(dev);
241 return status;
242 }
243
244 zxlogf(TRACE, "acpi-thermal: initialized '%s' %u trip points\n",
245 name, dev->trip_point_count);
246
247 return ZX_OK;
248 }
249