1 /* $OpenBSD: ampchwm.c,v 1.1 2023/12/11 11:15:44 claudio Exp $ */
2 /*
3 * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #include <sys/param.h>
18 #include <sys/systm.h>
19 #include <sys/types.h>
20 #include <sys/device.h>
21 #include <sys/malloc.h>
22 #include <sys/sensors.h>
23
24 #include <dev/acpi/acpireg.h>
25 #include <dev/acpi/acpivar.h>
26 #include <dev/acpi/amltypes.h>
27
28 int ampchwm_match(struct device *, void *, void *);
29 void ampchwm_attach(struct device *, struct device *, void *);
30
31 #define HWMON_ID 0x304d5748
32 #define HWMON_UNIT_CELSIUS 0x01
33 #define HWMON_UNIT_JOULES 0x10
34 #define HWMON_UNIT_MILIJOULES 0x11
35 #define HWMON_UNIT_MICROJOULES 0x12
36 #define HWMON_MAX_METRIC_COUNT 2
37
38 union metrics_hdr {
39 uint64_t data;
40 struct {
41 uint32_t id;
42 uint16_t version;
43 uint16_t count;
44 };
45 };
46
47 union metric_hdr {
48 uint64_t data[3];
49 struct {
50 char label[16];
51 uint8_t unit;
52 uint8_t data_size;
53 uint16_t data_count;
54 uint32_t pad;
55 };
56 };
57
58
59 struct ampchwm_softc {
60 struct device sc_dev;
61 struct acpi_softc *sc_acpi;
62 struct aml_node *sc_node;
63
64 bus_space_tag_t sc_iot;
65 bus_space_handle_t sc_ioh;
66 size_t sc_size;
67
68 uint16_t sc_count;
69 struct {
70 struct ksensor *sc_sens;
71 uint16_t sc_sens_offset;
72 uint16_t sc_sens_count;
73 uint16_t sc_sens_size;
74 uint16_t sc_sens_unit;
75 } sc_metrics[HWMON_MAX_METRIC_COUNT];
76
77 struct ksensordev sc_sensdev;
78 struct sensor_task *sc_sens_task;
79 };
80
81 const struct cfattach ampchwm_ca = {
82 sizeof(struct ampchwm_softc), ampchwm_match, ampchwm_attach
83 };
84
85 struct cfdriver ampchwm_cd = {
86 NULL, "ampchwm", DV_DULL
87 };
88
89 const char *ampchwm_hids[] = {
90 "AMPC0005",
91 NULL
92 };
93
94 int ampchwm_attach_sensors(struct ampchwm_softc *, int,
95 union metric_hdr *, uint16_t *);
96 void ampchwm_refresh_sensors(void *);
97 void ampchwm_update_sensor(struct ampchwm_softc *, int, int);
98
99
100 int
ampchwm_match(struct device * parent,void * match,void * aux)101 ampchwm_match(struct device *parent, void *match, void *aux)
102 {
103 struct acpi_attach_args *aaa = aux;
104 struct cfdata *cf = match;
105
106 if (aaa->aaa_naddr < 1)
107 return (0);
108 return (acpi_matchhids(aaa, ampchwm_hids, cf->cf_driver->cd_name));
109 }
110
111 void
ampchwm_attach(struct device * parent,struct device * self,void * aux)112 ampchwm_attach(struct device *parent, struct device *self, void *aux)
113 {
114 struct ampchwm_softc *sc = (struct ampchwm_softc *)self;
115 struct acpi_attach_args *aaa = aux;
116 union metrics_hdr hdr;
117 union metric_hdr metric;
118 uint16_t offset = 0;
119 int i;
120
121 sc->sc_acpi = (struct acpi_softc *)parent;
122 sc->sc_node = aaa->aaa_node;
123
124 printf(" %s", sc->sc_node->name);
125 printf(" addr 0x%llx/0x%llx", aaa->aaa_addr[0], aaa->aaa_size[0]);
126
127 sc->sc_iot = aaa->aaa_bst[0];
128 sc->sc_size = aaa->aaa_size[0];
129
130 if (bus_space_map(sc->sc_iot, aaa->aaa_addr[0], aaa->aaa_size[0],
131 0, &sc->sc_ioh)) {
132 printf(": can't map registers\n");
133 return;
134 }
135
136 bus_space_read_region_8(sc->sc_iot, sc->sc_ioh, offset, &hdr.data, 1);
137
138 if (hdr.id != HWMON_ID) {
139 printf(": bad id %x\n", hdr.id);
140 goto unmap;
141 }
142
143 printf(": ver %d", hdr.version);
144
145 strlcpy(sc->sc_sensdev.xname, sc->sc_dev.dv_xname,
146 sizeof(sc->sc_sensdev.xname));
147
148 offset += sizeof(hdr);
149 for (i = 0; i < hdr.count; i++) {
150 bus_space_read_region_8(sc->sc_iot, sc->sc_ioh, offset,
151 metric.data, 3);
152 if (ampchwm_attach_sensors(sc, i, &metric, &offset))
153 goto unmap;
154 }
155 sc->sc_count = MIN(hdr.count, HWMON_MAX_METRIC_COUNT);
156
157 sensordev_install(&sc->sc_sensdev);
158 sc->sc_sens_task = sensor_task_register(sc, ampchwm_refresh_sensors, 1);
159 printf("\n");
160
161 return;
162 unmap:
163 bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size);
164 return;
165 }
166
167 int
ampchwm_attach_sensors(struct ampchwm_softc * sc,int num,union metric_hdr * metric,uint16_t * offsetp)168 ampchwm_attach_sensors(struct ampchwm_softc *sc, int num,
169 union metric_hdr *metric, uint16_t *offsetp)
170 {
171 uint16_t off = *offsetp;
172 int i, count = 0;
173
174 if (num >= HWMON_MAX_METRIC_COUNT) {
175 if (num == HWMON_MAX_METRIC_COUNT)
176 printf(" ignoring extra metrics");
177 return 0;
178 }
179
180 off += sizeof(*metric);
181 /* skip 0 values since those are disabled cores */
182 for (i = 0; i < metric->data_count; i++) {
183 if (bus_space_read_8(sc->sc_iot, sc->sc_ioh,
184 off + i * 8) == 0)
185 continue;
186 count++;
187 }
188
189 sc->sc_metrics[num].sc_sens = mallocarray(count,
190 sizeof(struct ksensor), M_DEVBUF, M_NOWAIT);
191 if (sc->sc_metrics[num].sc_sens == NULL) {
192 printf(" out of memory\n");
193 return -1;
194 }
195
196 sc->sc_metrics[num].sc_sens_offset = off;
197 sc->sc_metrics[num].sc_sens_count = count;
198 sc->sc_metrics[num].sc_sens_unit = metric->unit;
199 if (metric->data_size == 0)
200 sc->sc_metrics[num].sc_sens_size = 8;
201 else
202 sc->sc_metrics[num].sc_sens_size = 4;
203
204 for (i = 0; i < count; i++) {
205 struct ksensor *s = &sc->sc_metrics[num].sc_sens[i];
206
207 strlcpy(s->desc, metric->label, sizeof(s->desc));
208 if (metric->unit == HWMON_UNIT_CELSIUS)
209 s->type = SENSOR_TEMP;
210 else
211 s->type = SENSOR_ENERGY;
212 sensor_attach(&sc->sc_sensdev, s);
213
214 ampchwm_update_sensor(sc, num, i);
215 }
216
217 off += metric->data_count * 8;
218
219 printf(", %d \"%s\"", count, metric->label);
220 *offsetp = off;
221 return 0;
222 }
223
224 void
ampchwm_refresh_sensors(void * arg)225 ampchwm_refresh_sensors(void *arg)
226 {
227 struct ampchwm_softc *sc = arg;
228 int num, i;
229
230 for (num = 0; num < sc->sc_count; num++)
231 for (i = 0; i < sc->sc_metrics[num].sc_sens_count; i++)
232 ampchwm_update_sensor(sc, num, i);
233 }
234
235 void
ampchwm_update_sensor(struct ampchwm_softc * sc,int num,int i)236 ampchwm_update_sensor(struct ampchwm_softc *sc, int num, int i)
237 {
238 struct ksensor *s;
239 uint64_t v;
240
241 KASSERT(i < sc->sc_metrics[num].sc_sens_count);
242
243 s = &sc->sc_metrics[num].sc_sens[i];
244 if (sc->sc_metrics[num].sc_sens_size == 8) {
245 v = bus_space_read_8(sc->sc_iot, sc->sc_ioh,
246 sc->sc_metrics[num].sc_sens_offset + i * sizeof(v));
247 } else {
248 v = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
249 sc->sc_metrics[num].sc_sens_offset + i * sizeof(v));
250 }
251
252 if (v == 0) {
253 s->flags = SENSOR_FUNKNOWN;
254 s->status = SENSOR_S_UNKNOWN;
255 } else {
256 s->flags = 0;
257 s->status = SENSOR_S_OK;
258 }
259
260 switch (sc->sc_metrics[num].sc_sens_unit) {
261 case HWMON_UNIT_CELSIUS:
262 s->value = v * 1000 * 1000 + 273150000;
263 break;
264 case HWMON_UNIT_JOULES:
265 v *= 1000;
266 case HWMON_UNIT_MILIJOULES:
267 v *= 1000;
268 case HWMON_UNIT_MICROJOULES:
269 s->value = v;
270 break;
271 }
272 }
273