1/*
2 * Copyright 2013 Red Hat Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
18 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20 * OTHER DEALINGS IN THE SOFTWARE.
21 *
22 * Authors: Ben Skeggs
23 */
24#include "priv.h"
25
26#include <core/client.h>
27#include <core/option.h>
28
29#include <nvif/class.h>
30#include <nvif/if0002.h>
31#include <nvif/if0003.h>
32#include <nvif/ioctl.h>
33#include <nvif/unpack.h>
34
35static u8
36nvkm_pm_count_perfdom(struct nvkm_pm *pm)
37{
38	struct nvkm_perfdom *dom;
39	u8 domain_nr = 0;
40
41	list_for_each_entry(dom, &pm->domains, head)
42		domain_nr++;
43	return domain_nr;
44}
45
46static u16
47nvkm_perfdom_count_perfsig(struct nvkm_perfdom *dom)
48{
49	u16 signal_nr = 0;
50	int i;
51
52	if (dom) {
53		for (i = 0; i < dom->signal_nr; i++) {
54			if (dom->signal[i].name)
55				signal_nr++;
56		}
57	}
58	return signal_nr;
59}
60
61static struct nvkm_perfdom *
62nvkm_perfdom_find(struct nvkm_pm *pm, int di)
63{
64	struct nvkm_perfdom *dom;
65	int tmp = 0;
66
67	list_for_each_entry(dom, &pm->domains, head) {
68		if (tmp++ == di)
69			return dom;
70	}
71	return NULL;
72}
73
74static struct nvkm_perfsig *
75nvkm_perfsig_find(struct nvkm_pm *pm, u8 di, u8 si, struct nvkm_perfdom **pdom)
76{
77	struct nvkm_perfdom *dom = *pdom;
78
79	if (dom == NULL) {
80		dom = nvkm_perfdom_find(pm, di);
81		if (dom == NULL)
82			return NULL;
83		*pdom = dom;
84	}
85
86	if (!dom->signal[si].name)
87		return NULL;
88	return &dom->signal[si];
89}
90
91static u8
92nvkm_perfsig_count_perfsrc(struct nvkm_perfsig *sig)
93{
94	u8 source_nr = 0, i;
95
96	for (i = 0; i < ARRAY_SIZE(sig->source); i++) {
97		if (sig->source[i])
98			source_nr++;
99	}
100	return source_nr;
101}
102
103static struct nvkm_perfsrc *
104nvkm_perfsrc_find(struct nvkm_pm *pm, struct nvkm_perfsig *sig, int si)
105{
106	struct nvkm_perfsrc *src;
107	bool found = false;
108	int tmp = 1; /* Sources ID start from 1 */
109	u8 i;
110
111	for (i = 0; i < ARRAY_SIZE(sig->source) && sig->source[i]; i++) {
112		if (sig->source[i] == si) {
113			found = true;
114			break;
115		}
116	}
117
118	if (found) {
119		list_for_each_entry(src, &pm->sources, head) {
120			if (tmp++ == si)
121				return src;
122		}
123	}
124
125	return NULL;
126}
127
128static int
129nvkm_perfsrc_enable(struct nvkm_pm *pm, struct nvkm_perfctr *ctr)
130{
131	struct nvkm_subdev *subdev = &pm->engine.subdev;
132	struct nvkm_device *device = subdev->device;
133	struct nvkm_perfdom *dom = NULL;
134	struct nvkm_perfsig *sig;
135	struct nvkm_perfsrc *src;
136	u32 mask, value;
137	int i, j;
138
139	for (i = 0; i < 4; i++) {
140		for (j = 0; j < 8 && ctr->source[i][j]; j++) {
141			sig = nvkm_perfsig_find(pm, ctr->domain,
142						ctr->signal[i], &dom);
143			if (!sig)
144				return -EINVAL;
145
146			src = nvkm_perfsrc_find(pm, sig, ctr->source[i][j]);
147			if (!src)
148				return -EINVAL;
149
150			/* set enable bit if needed */
151			mask = value = 0x00000000;
152			if (src->enable)
153				mask = value = 0x80000000;
154			mask  |= (src->mask << src->shift);
155			value |= ((ctr->source[i][j] >> 32) << src->shift);
156
157			/* enable the source */
158			nvkm_mask(device, src->addr, mask, value);
159			nvkm_debug(subdev,
160				   "enabled source %08x %08x %08x\n",
161				   src->addr, mask, value);
162		}
163	}
164	return 0;
165}
166
167static int
168nvkm_perfsrc_disable(struct nvkm_pm *pm, struct nvkm_perfctr *ctr)
169{
170	struct nvkm_subdev *subdev = &pm->engine.subdev;
171	struct nvkm_device *device = subdev->device;
172	struct nvkm_perfdom *dom = NULL;
173	struct nvkm_perfsig *sig;
174	struct nvkm_perfsrc *src;
175	u32 mask;
176	int i, j;
177
178	for (i = 0; i < 4; i++) {
179		for (j = 0; j < 8 && ctr->source[i][j]; j++) {
180			sig = nvkm_perfsig_find(pm, ctr->domain,
181						ctr->signal[i], &dom);
182			if (!sig)
183				return -EINVAL;
184
185			src = nvkm_perfsrc_find(pm, sig, ctr->source[i][j]);
186			if (!src)
187				return -EINVAL;
188
189			/* unset enable bit if needed */
190			mask = 0x00000000;
191			if (src->enable)
192				mask = 0x80000000;
193			mask |= (src->mask << src->shift);
194
195			/* disable the source */
196			nvkm_mask(device, src->addr, mask, 0);
197			nvkm_debug(subdev, "disabled source %08x %08x\n",
198				   src->addr, mask);
199		}
200	}
201	return 0;
202}
203
204/*******************************************************************************
205 * Perfdom object classes
206 ******************************************************************************/
207static int
208nvkm_perfdom_init(struct nvkm_perfdom *dom, void *data, u32 size)
209{
210	union {
211		struct nvif_perfdom_init none;
212	} *args = data;
213	struct nvkm_object *object = &dom->object;
214	struct nvkm_pm *pm = dom->perfmon->pm;
215	int ret = -ENOSYS, i;
216
217	nvif_ioctl(object, "perfdom init size %d\n", size);
218	if (!(ret = nvif_unvers(ret, &data, &size, args->none))) {
219		nvif_ioctl(object, "perfdom init\n");
220	} else
221		return ret;
222
223	for (i = 0; i < 4; i++) {
224		if (dom->ctr[i]) {
225			dom->func->init(pm, dom, dom->ctr[i]);
226
227			/* enable sources */
228			nvkm_perfsrc_enable(pm, dom->ctr[i]);
229		}
230	}
231
232	/* start next batch of counters for sampling */
233	dom->func->next(pm, dom);
234	return 0;
235}
236
237static int
238nvkm_perfdom_sample(struct nvkm_perfdom *dom, void *data, u32 size)
239{
240	union {
241		struct nvif_perfdom_sample none;
242	} *args = data;
243	struct nvkm_object *object = &dom->object;
244	struct nvkm_pm *pm = dom->perfmon->pm;
245	int ret = -ENOSYS;
246
247	nvif_ioctl(object, "perfdom sample size %d\n", size);
248	if (!(ret = nvif_unvers(ret, &data, &size, args->none))) {
249		nvif_ioctl(object, "perfdom sample\n");
250	} else
251		return ret;
252	pm->sequence++;
253
254	/* sample previous batch of counters */
255	list_for_each_entry(dom, &pm->domains, head)
256		dom->func->next(pm, dom);
257
258	return 0;
259}
260
261static int
262nvkm_perfdom_read(struct nvkm_perfdom *dom, void *data, u32 size)
263{
264	union {
265		struct nvif_perfdom_read_v0 v0;
266	} *args = data;
267	struct nvkm_object *object = &dom->object;
268	struct nvkm_pm *pm = dom->perfmon->pm;
269	int ret = -ENOSYS, i;
270
271	nvif_ioctl(object, "perfdom read size %d\n", size);
272	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
273		nvif_ioctl(object, "perfdom read vers %d\n", args->v0.version);
274	} else
275		return ret;
276
277	for (i = 0; i < 4; i++) {
278		if (dom->ctr[i])
279			dom->func->read(pm, dom, dom->ctr[i]);
280	}
281
282	if (!dom->clk)
283		return -EAGAIN;
284
285	for (i = 0; i < 4; i++)
286		if (dom->ctr[i])
287			args->v0.ctr[i] = dom->ctr[i]->ctr;
288	args->v0.clk = dom->clk;
289	return 0;
290}
291
292static int
293nvkm_perfdom_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size)
294{
295	struct nvkm_perfdom *dom = nvkm_perfdom(object);
296	switch (mthd) {
297	case NVIF_PERFDOM_V0_INIT:
298		return nvkm_perfdom_init(dom, data, size);
299	case NVIF_PERFDOM_V0_SAMPLE:
300		return nvkm_perfdom_sample(dom, data, size);
301	case NVIF_PERFDOM_V0_READ:
302		return nvkm_perfdom_read(dom, data, size);
303	default:
304		break;
305	}
306	return -EINVAL;
307}
308
309static void *
310nvkm_perfdom_dtor(struct nvkm_object *object)
311{
312	struct nvkm_perfdom *dom = nvkm_perfdom(object);
313	struct nvkm_pm *pm = dom->perfmon->pm;
314	int i;
315
316	for (i = 0; i < 4; i++) {
317		struct nvkm_perfctr *ctr = dom->ctr[i];
318		if (ctr) {
319			nvkm_perfsrc_disable(pm, ctr);
320			if (ctr->head.next)
321				list_del(&ctr->head);
322		}
323		kfree(ctr);
324	}
325
326	return dom;
327}
328
329static int
330nvkm_perfctr_new(struct nvkm_perfdom *dom, int slot, u8 domain,
331		 struct nvkm_perfsig *signal[4], u64 source[4][8],
332		 u16 logic_op, struct nvkm_perfctr **pctr)
333{
334	struct nvkm_perfctr *ctr;
335	int i, j;
336
337	if (!dom)
338		return -EINVAL;
339
340	ctr = *pctr = kzalloc(sizeof(*ctr), GFP_KERNEL);
341	if (!ctr)
342		return -ENOMEM;
343
344	ctr->domain   = domain;
345	ctr->logic_op = logic_op;
346	ctr->slot     = slot;
347	for (i = 0; i < 4; i++) {
348		if (signal[i]) {
349			ctr->signal[i] = signal[i] - dom->signal;
350			for (j = 0; j < 8; j++)
351				ctr->source[i][j] = source[i][j];
352		}
353	}
354	list_add_tail(&ctr->head, &dom->list);
355
356	return 0;
357}
358
359static const struct nvkm_object_func
360nvkm_perfdom = {
361	.dtor = nvkm_perfdom_dtor,
362	.mthd = nvkm_perfdom_mthd,
363};
364
365static int
366nvkm_perfdom_new_(struct nvkm_perfmon *perfmon,
367		  const struct nvkm_oclass *oclass, void *data, u32 size,
368		  struct nvkm_object **pobject)
369{
370	union {
371		struct nvif_perfdom_v0 v0;
372	} *args = data;
373	struct nvkm_pm *pm = perfmon->pm;
374	struct nvkm_object *parent = oclass->parent;
375	struct nvkm_perfdom *sdom = NULL;
376	struct nvkm_perfctr *ctr[4] = {};
377	struct nvkm_perfdom *dom;
378	int c, s, m;
379	int ret = -ENOSYS;
380
381	nvif_ioctl(parent, "create perfdom size %d\n", size);
382	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
383		nvif_ioctl(parent, "create perfdom vers %d dom %d mode %02x\n",
384			   args->v0.version, args->v0.domain, args->v0.mode);
385	} else
386		return ret;
387
388	for (c = 0; c < ARRAY_SIZE(args->v0.ctr); c++) {
389		struct nvkm_perfsig *sig[4] = {};
390		u64 src[4][8] = {};
391
392		for (s = 0; s < ARRAY_SIZE(args->v0.ctr[c].signal); s++) {
393			sig[s] = nvkm_perfsig_find(pm, args->v0.domain,
394						   args->v0.ctr[c].signal[s],
395						   &sdom);
396			if (args->v0.ctr[c].signal[s] && !sig[s])
397				return -EINVAL;
398
399			for (m = 0; m < 8; m++) {
400				src[s][m] = args->v0.ctr[c].source[s][m];
401				if (src[s][m] && !nvkm_perfsrc_find(pm, sig[s],
402							            src[s][m]))
403					return -EINVAL;
404			}
405		}
406
407		ret = nvkm_perfctr_new(sdom, c, args->v0.domain, sig, src,
408				       args->v0.ctr[c].logic_op, &ctr[c]);
409		if (ret)
410			return ret;
411	}
412
413	if (!sdom)
414		return -EINVAL;
415
416	if (!(dom = kzalloc(sizeof(*dom), GFP_KERNEL)))
417		return -ENOMEM;
418	nvkm_object_ctor(&nvkm_perfdom, oclass, &dom->object);
419	dom->perfmon = perfmon;
420	*pobject = &dom->object;
421
422	dom->func = sdom->func;
423	dom->addr = sdom->addr;
424	dom->mode = args->v0.mode;
425	for (c = 0; c < ARRAY_SIZE(ctr); c++)
426		dom->ctr[c] = ctr[c];
427	return 0;
428}
429
430/*******************************************************************************
431 * Perfmon object classes
432 ******************************************************************************/
433static int
434nvkm_perfmon_mthd_query_domain(struct nvkm_perfmon *perfmon,
435			       void *data, u32 size)
436{
437	union {
438		struct nvif_perfmon_query_domain_v0 v0;
439	} *args = data;
440	struct nvkm_object *object = &perfmon->object;
441	struct nvkm_pm *pm = perfmon->pm;
442	struct nvkm_perfdom *dom;
443	u8 domain_nr;
444	int di, ret = -ENOSYS;
445
446	nvif_ioctl(object, "perfmon query domain size %d\n", size);
447	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
448		nvif_ioctl(object, "perfmon domain vers %d iter %02x\n",
449			   args->v0.version, args->v0.iter);
450		di = (args->v0.iter & 0xff) - 1;
451	} else
452		return ret;
453
454	domain_nr = nvkm_pm_count_perfdom(pm);
455	if (di >= (int)domain_nr)
456		return -EINVAL;
457
458	if (di >= 0) {
459		dom = nvkm_perfdom_find(pm, di);
460		if (dom == NULL)
461			return -EINVAL;
462
463		args->v0.id         = di;
464		args->v0.signal_nr  = nvkm_perfdom_count_perfsig(dom);
465		strscpy(args->v0.name, dom->name, sizeof(args->v0.name));
466
467		/* Currently only global counters (PCOUNTER) are implemented
468		 * but this will be different for local counters (MP). */
469		args->v0.counter_nr = 4;
470	}
471
472	if (++di < domain_nr) {
473		args->v0.iter = ++di;
474		return 0;
475	}
476
477	args->v0.iter = 0xff;
478	return 0;
479}
480
481static int
482nvkm_perfmon_mthd_query_signal(struct nvkm_perfmon *perfmon,
483			       void *data, u32 size)
484{
485	union {
486		struct nvif_perfmon_query_signal_v0 v0;
487	} *args = data;
488	struct nvkm_object *object = &perfmon->object;
489	struct nvkm_pm *pm = perfmon->pm;
490	struct nvkm_device *device = pm->engine.subdev.device;
491	struct nvkm_perfdom *dom;
492	struct nvkm_perfsig *sig;
493	const bool all = nvkm_boolopt(device->cfgopt, "NvPmShowAll", false);
494	const bool raw = nvkm_boolopt(device->cfgopt, "NvPmUnnamed", all);
495	int ret = -ENOSYS, si;
496
497	nvif_ioctl(object, "perfmon query signal size %d\n", size);
498	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
499		nvif_ioctl(object,
500			   "perfmon query signal vers %d dom %d iter %04x\n",
501			   args->v0.version, args->v0.domain, args->v0.iter);
502		si = (args->v0.iter & 0xffff) - 1;
503	} else
504		return ret;
505
506	dom = nvkm_perfdom_find(pm, args->v0.domain);
507	if (dom == NULL || si >= (int)dom->signal_nr)
508		return -EINVAL;
509
510	if (si >= 0) {
511		sig = &dom->signal[si];
512		if (raw || !sig->name) {
513			snprintf(args->v0.name, sizeof(args->v0.name),
514				 "/%s/%02x", dom->name, si);
515		} else {
516			strscpy(args->v0.name, sig->name, sizeof(args->v0.name));
517		}
518
519		args->v0.signal = si;
520		args->v0.source_nr = nvkm_perfsig_count_perfsrc(sig);
521	}
522
523	while (++si < dom->signal_nr) {
524		if (all || dom->signal[si].name) {
525			args->v0.iter = ++si;
526			return 0;
527		}
528	}
529
530	args->v0.iter = 0xffff;
531	return 0;
532}
533
534static int
535nvkm_perfmon_mthd_query_source(struct nvkm_perfmon *perfmon,
536			       void *data, u32 size)
537{
538	union {
539		struct nvif_perfmon_query_source_v0 v0;
540	} *args = data;
541	struct nvkm_object *object = &perfmon->object;
542	struct nvkm_pm *pm = perfmon->pm;
543	struct nvkm_perfdom *dom = NULL;
544	struct nvkm_perfsig *sig;
545	struct nvkm_perfsrc *src;
546	u8 source_nr = 0;
547	int si, ret = -ENOSYS;
548
549	nvif_ioctl(object, "perfmon query source size %d\n", size);
550	if (!(ret = nvif_unpack(ret, &data, &size, args->v0, 0, 0, false))) {
551		nvif_ioctl(object,
552			   "perfmon source vers %d dom %d sig %02x iter %02x\n",
553			   args->v0.version, args->v0.domain, args->v0.signal,
554			   args->v0.iter);
555		si = (args->v0.iter & 0xff) - 1;
556	} else
557		return ret;
558
559	sig = nvkm_perfsig_find(pm, args->v0.domain, args->v0.signal, &dom);
560	if (!sig)
561		return -EINVAL;
562
563	source_nr = nvkm_perfsig_count_perfsrc(sig);
564	if (si >= (int)source_nr)
565		return -EINVAL;
566
567	if (si >= 0) {
568		src = nvkm_perfsrc_find(pm, sig, sig->source[si]);
569		if (!src)
570			return -EINVAL;
571
572		args->v0.source = sig->source[si];
573		args->v0.mask   = src->mask;
574		strscpy(args->v0.name, src->name, sizeof(args->v0.name));
575	}
576
577	if (++si < source_nr) {
578		args->v0.iter = ++si;
579		return 0;
580	}
581
582	args->v0.iter = 0xff;
583	return 0;
584}
585
586static int
587nvkm_perfmon_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size)
588{
589	struct nvkm_perfmon *perfmon = nvkm_perfmon(object);
590	switch (mthd) {
591	case NVIF_PERFMON_V0_QUERY_DOMAIN:
592		return nvkm_perfmon_mthd_query_domain(perfmon, data, size);
593	case NVIF_PERFMON_V0_QUERY_SIGNAL:
594		return nvkm_perfmon_mthd_query_signal(perfmon, data, size);
595	case NVIF_PERFMON_V0_QUERY_SOURCE:
596		return nvkm_perfmon_mthd_query_source(perfmon, data, size);
597	default:
598		break;
599	}
600	return -EINVAL;
601}
602
603static int
604nvkm_perfmon_child_new(const struct nvkm_oclass *oclass, void *data, u32 size,
605		       struct nvkm_object **pobject)
606{
607	struct nvkm_perfmon *perfmon = nvkm_perfmon(oclass->parent);
608	return nvkm_perfdom_new_(perfmon, oclass, data, size, pobject);
609}
610
611static int
612nvkm_perfmon_child_get(struct nvkm_object *object, int index,
613		       struct nvkm_oclass *oclass)
614{
615	if (index == 0) {
616		oclass->base.oclass = NVIF_CLASS_PERFDOM;
617		oclass->base.minver = 0;
618		oclass->base.maxver = 0;
619		oclass->ctor = nvkm_perfmon_child_new;
620		return 0;
621	}
622	return -EINVAL;
623}
624
625static void *
626nvkm_perfmon_dtor(struct nvkm_object *object)
627{
628	struct nvkm_perfmon *perfmon = nvkm_perfmon(object);
629	struct nvkm_pm *pm = perfmon->pm;
630	spin_lock(&pm->client.lock);
631	if (pm->client.object == &perfmon->object)
632		pm->client.object = NULL;
633	spin_unlock(&pm->client.lock);
634	return perfmon;
635}
636
637static const struct nvkm_object_func
638nvkm_perfmon = {
639	.dtor = nvkm_perfmon_dtor,
640	.mthd = nvkm_perfmon_mthd,
641	.sclass = nvkm_perfmon_child_get,
642};
643
644static int
645nvkm_perfmon_new(struct nvkm_pm *pm, const struct nvkm_oclass *oclass,
646		 void *data, u32 size, struct nvkm_object **pobject)
647{
648	struct nvkm_perfmon *perfmon;
649
650	if (!(perfmon = kzalloc(sizeof(*perfmon), GFP_KERNEL)))
651		return -ENOMEM;
652	nvkm_object_ctor(&nvkm_perfmon, oclass, &perfmon->object);
653	perfmon->pm = pm;
654	*pobject = &perfmon->object;
655	return 0;
656}
657
658/*******************************************************************************
659 * PPM engine/subdev functions
660 ******************************************************************************/
661
662static int
663nvkm_pm_oclass_new(struct nvkm_device *device, const struct nvkm_oclass *oclass,
664		   void *data, u32 size, struct nvkm_object **pobject)
665{
666	struct nvkm_pm *pm = nvkm_pm(oclass->engine);
667	int ret;
668
669	ret = nvkm_perfmon_new(pm, oclass, data, size, pobject);
670	if (ret)
671		return ret;
672
673	spin_lock(&pm->client.lock);
674	if (pm->client.object == NULL)
675		pm->client.object = *pobject;
676	ret = (pm->client.object == *pobject) ? 0 : -EBUSY;
677	spin_unlock(&pm->client.lock);
678	return ret;
679}
680
681static const struct nvkm_device_oclass
682nvkm_pm_oclass = {
683	.base.oclass = NVIF_CLASS_PERFMON,
684	.base.minver = -1,
685	.base.maxver = -1,
686	.ctor = nvkm_pm_oclass_new,
687};
688
689static int
690nvkm_pm_oclass_get(struct nvkm_oclass *oclass, int index,
691		   const struct nvkm_device_oclass **class)
692{
693	if (index == 0) {
694		oclass->base = nvkm_pm_oclass.base;
695		*class = &nvkm_pm_oclass;
696		return index;
697	}
698	return 1;
699}
700
701static int
702nvkm_perfsrc_new(struct nvkm_pm *pm, struct nvkm_perfsig *sig,
703		 const struct nvkm_specsrc *spec)
704{
705	const struct nvkm_specsrc *ssrc;
706	const struct nvkm_specmux *smux;
707	struct nvkm_perfsrc *src;
708	u8 source_nr = 0;
709
710	if (!spec) {
711		/* No sources are defined for this signal. */
712		return 0;
713	}
714
715	ssrc = spec;
716	while (ssrc->name) {
717		smux = ssrc->mux;
718		while (smux->name) {
719			bool found = false;
720			u8 source_id = 0;
721			u32 len;
722
723			list_for_each_entry(src, &pm->sources, head) {
724				if (src->addr == ssrc->addr &&
725				    src->shift == smux->shift) {
726					found = true;
727					break;
728				}
729				source_id++;
730			}
731
732			if (!found) {
733				src = kzalloc(sizeof(*src), GFP_KERNEL);
734				if (!src)
735					return -ENOMEM;
736
737				src->addr   = ssrc->addr;
738				src->mask   = smux->mask;
739				src->shift  = smux->shift;
740				src->enable = smux->enable;
741
742				len = strlen(ssrc->name) +
743				      strlen(smux->name) + 2;
744				src->name = kzalloc(len, GFP_KERNEL);
745				if (!src->name) {
746					kfree(src);
747					return -ENOMEM;
748				}
749				snprintf(src->name, len, "%s_%s", ssrc->name,
750					 smux->name);
751
752				list_add_tail(&src->head, &pm->sources);
753			}
754
755			sig->source[source_nr++] = source_id + 1;
756			smux++;
757		}
758		ssrc++;
759	}
760
761	return 0;
762}
763
764int
765nvkm_perfdom_new(struct nvkm_pm *pm, const char *name, u32 mask,
766		 u32 base, u32 size_unit, u32 size_domain,
767		 const struct nvkm_specdom *spec)
768{
769	const struct nvkm_specdom *sdom;
770	const struct nvkm_specsig *ssig;
771	struct nvkm_perfdom *dom;
772	int ret, i;
773
774	for (i = 0; i == 0 || mask; i++) {
775		u32 addr = base + (i * size_unit);
776		if (i && !(mask & (1 << i)))
777			continue;
778
779		sdom = spec;
780		while (sdom->signal_nr) {
781			dom = kzalloc(struct_size(dom, signal, sdom->signal_nr),
782				      GFP_KERNEL);
783			if (!dom)
784				return -ENOMEM;
785
786			if (mask) {
787				snprintf(dom->name, sizeof(dom->name),
788					 "%s/%02x/%02x", name, i,
789					 (int)(sdom - spec));
790			} else {
791				snprintf(dom->name, sizeof(dom->name),
792					 "%s/%02x", name, (int)(sdom - spec));
793			}
794
795			list_add_tail(&dom->head, &pm->domains);
796			INIT_LIST_HEAD(&dom->list);
797			dom->func = sdom->func;
798			dom->addr = addr;
799			dom->signal_nr = sdom->signal_nr;
800
801			ssig = (sdom++)->signal;
802			while (ssig->name) {
803				struct nvkm_perfsig *sig =
804					&dom->signal[ssig->signal];
805				sig->name = ssig->name;
806				ret = nvkm_perfsrc_new(pm, sig, ssig->source);
807				if (ret)
808					return ret;
809				ssig++;
810			}
811
812			addr += size_domain;
813		}
814
815		mask &= ~(1 << i);
816	}
817
818	return 0;
819}
820
821static int
822nvkm_pm_fini(struct nvkm_engine *engine, bool suspend)
823{
824	struct nvkm_pm *pm = nvkm_pm(engine);
825	if (pm->func->fini)
826		pm->func->fini(pm);
827	return 0;
828}
829
830static void *
831nvkm_pm_dtor(struct nvkm_engine *engine)
832{
833	struct nvkm_pm *pm = nvkm_pm(engine);
834	struct nvkm_perfdom *dom, *next_dom;
835	struct nvkm_perfsrc *src, *next_src;
836
837	list_for_each_entry_safe(dom, next_dom, &pm->domains, head) {
838		list_del(&dom->head);
839		kfree(dom);
840	}
841
842	list_for_each_entry_safe(src, next_src, &pm->sources, head) {
843		list_del(&src->head);
844		kfree(src->name);
845		kfree(src);
846	}
847
848	return pm;
849}
850
851static const struct nvkm_engine_func
852nvkm_pm = {
853	.dtor = nvkm_pm_dtor,
854	.fini = nvkm_pm_fini,
855	.base.sclass = nvkm_pm_oclass_get,
856};
857
858int
859nvkm_pm_ctor(const struct nvkm_pm_func *func, struct nvkm_device *device,
860	     enum nvkm_subdev_type type, int inst, struct nvkm_pm *pm)
861{
862	pm->func = func;
863	INIT_LIST_HEAD(&pm->domains);
864	INIT_LIST_HEAD(&pm->sources);
865	spin_lock_init(&pm->client.lock);
866	return nvkm_engine_ctor(&nvkm_pm, device, type, inst, true, &pm->engine);
867}
868