1/*	$NetBSD: obio.c,v 1.53 2022/12/28 07:34:42 macallan Exp $	*/
2
3/*-
4 * Copyright (C) 1998	Internet Research Institute, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *	This product includes software developed by
18 *	Internet Research Institute, Inc.
19 * 4. The name of the author may not be used to endorse or promote products
20 *    derived from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <sys/cdefs.h>
35__KERNEL_RCSID(0, "$NetBSD: obio.c,v 1.53 2022/12/28 07:34:42 macallan Exp $");
36
37#include <sys/param.h>
38#include <sys/systm.h>
39#include <sys/kernel.h>
40#include <sys/device.h>
41#include <sys/sysctl.h>
42
43#include <dev/pci/pcivar.h>
44#include <dev/pci/pcidevs.h>
45
46#include <dev/ofw/openfirm.h>
47
48#include <machine/autoconf.h>
49
50#include <macppc/dev/obiovar.h>
51
52#include <powerpc/cpu.h>
53#include <sys/cpufreq.h>
54
55#include "opt_obio.h"
56
57#ifdef OBIO_DEBUG
58# define DPRINTF printf
59#else
60# define DPRINTF while (0) printf
61#endif
62
63static void obio_attach(device_t, device_t, void *);
64static int obio_match(device_t, cfdata_t, void *);
65static int obio_print(void *, const char *);
66
67struct obio_softc {
68	device_t sc_dev;
69	bus_space_tag_t sc_tag;
70	bus_space_handle_t sc_bh;
71	int sc_node;
72#ifdef OBIO_SPEED_CONTROL
73	int sc_voltage;
74	int sc_busspeed;
75	int sc_spd_hi, sc_spd_lo;
76	struct cpufreq sc_cf;
77#endif
78};
79
80static struct obio_softc *obio0 = NULL;
81
82#ifdef OBIO_SPEED_CONTROL
83static void obio_setup_gpios(struct obio_softc *, int);
84static void obio_set_cpu_speed(struct obio_softc *, int);
85static int  obio_get_cpu_speed(struct obio_softc *);
86static int  sysctl_cpuspeed_temp(SYSCTLFN_ARGS);
87static int  sysctl_cpuspeed_cur(SYSCTLFN_ARGS);
88static int  sysctl_cpuspeed_available(SYSCTLFN_ARGS);
89static void obio_get_freq(void *, void *);
90static void obio_set_freq(void *, void *);
91static const char *keylargo[] = {"Keylargo",
92				 "AAPL,Keylargo",
93				 NULL};
94
95#endif
96
97CFATTACH_DECL_NEW(obio, sizeof(struct obio_softc),
98    obio_match, obio_attach, NULL, NULL);
99
100int
101obio_match(device_t parent, cfdata_t cf, void *aux)
102{
103	struct pci_attach_args *pa = aux;
104
105	if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_APPLE)
106		switch (PCI_PRODUCT(pa->pa_id)) {
107		case PCI_PRODUCT_APPLE_GC:
108		case PCI_PRODUCT_APPLE_OHARE:
109		case PCI_PRODUCT_APPLE_HEATHROW:
110		case PCI_PRODUCT_APPLE_PADDINGTON:
111		case PCI_PRODUCT_APPLE_KEYLARGO:
112		case PCI_PRODUCT_APPLE_PANGEA_MACIO:
113		case PCI_PRODUCT_APPLE_INTREPID:
114		case PCI_PRODUCT_APPLE_K2:
115		case PCI_PRODUCT_APPLE_SHASTA:
116			return 1;
117		}
118
119	return 0;
120}
121
122/*
123 * Attach all the sub-devices we can find
124 */
125void
126obio_attach(device_t parent, device_t self, void *aux)
127{
128	struct obio_softc *sc = device_private(self);
129	struct pci_attach_args *pa = aux;
130	struct confargs ca;
131	bus_space_handle_t bsh;
132	int node, child, namelen, error;
133	u_int reg[20];
134	int intr[6], parent_intr = 0, parent_nintr = 0;
135	int map_size = 0x1000;
136	char name[32];
137	char compat[32];
138
139	sc->sc_dev = self;
140#ifdef OBIO_SPEED_CONTROL
141	sc->sc_voltage = -1;
142	sc->sc_busspeed = -1;
143	sc->sc_spd_lo = 600;
144	sc->sc_spd_hi = 800;
145#endif
146
147	switch (PCI_PRODUCT(pa->pa_id)) {
148
149	case PCI_PRODUCT_APPLE_GC:
150	case PCI_PRODUCT_APPLE_OHARE:
151	case PCI_PRODUCT_APPLE_HEATHROW:
152	case PCI_PRODUCT_APPLE_PADDINGTON:
153	case PCI_PRODUCT_APPLE_KEYLARGO:
154	case PCI_PRODUCT_APPLE_PANGEA_MACIO:
155	case PCI_PRODUCT_APPLE_INTREPID:
156		node = pcidev_to_ofdev(pa->pa_pc, pa->pa_tag);
157		if (node == -1)
158			node = OF_finddevice("mac-io");
159		if (node == -1)
160			node = OF_finddevice("/pci/mac-io");
161		break;
162	case PCI_PRODUCT_APPLE_K2:
163	case PCI_PRODUCT_APPLE_SHASTA:
164		node = OF_finddevice("mac-io");
165		map_size = 0x10000;
166		break;
167
168	default:
169		node = -1;
170		break;
171	}
172	if (node == -1)
173		panic("macio not found or unknown");
174
175	sc->sc_node = node;
176
177#if defined (PMAC_G5)
178	if (OF_getprop(node, "assigned-addresses", reg, sizeof(reg)) < 20)
179	{
180		return;
181	}
182#else
183	if (OF_getprop(node, "assigned-addresses", reg, sizeof(reg)) < 12)
184		return;
185#endif /* PMAC_G5 */
186
187	/*
188	 * XXX
189	 * This relies on the primary obio always attaching first which is
190	 * true on the PowerBook 3400c and similar machines but may or may
191	 * not work on others. We can't rely on the node name since Apple
192	 * didn't follow anything remotely resembling a consistent naming
193	 * scheme.
194	 */
195	if (obio0 == NULL)
196		obio0 = sc;
197
198	ca.ca_baseaddr = reg[2];
199	ca.ca_tag = pa->pa_memt;
200	sc->sc_tag = pa->pa_memt;
201	error = bus_space_map (pa->pa_memt, ca.ca_baseaddr, map_size, 0, &bsh);
202	if (error)
203		panic(": failed to map mac-io %#x", ca.ca_baseaddr);
204	sc->sc_bh = bsh;
205
206	printf(": addr 0x%x\n", ca.ca_baseaddr);
207
208	/* Enable internal modem (KeyLargo) */
209	if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_KEYLARGO) {
210		aprint_normal("%s: enabling KeyLargo internal modem\n",
211		    device_xname(self));
212		bus_space_write_4(ca.ca_tag, bsh, 0x40,
213		    bus_space_read_4(ca.ca_tag, bsh, 0x40) & ~(1<<25));
214	}
215
216	/* Enable internal modem (Pangea) */
217	if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_PANGEA_MACIO) {
218		/* set reset */
219		bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x03, 0x04);
220		/* power modem on */
221		bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x02, 0x04);
222		/* unset reset */
223		bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x03, 0x05);
224	}
225
226	/* Gatwick and Paddington use same product ID */
227	namelen = OF_getprop(node, "compatible", compat, sizeof(compat));
228
229	if (strcmp(compat, "gatwick") == 0) {
230		parent_nintr = OF_getprop(node, "AAPL,interrupts", intr,
231					sizeof(intr));
232		parent_intr = intr[0];
233	} else {
234  		/* Enable CD and microphone sound input. */
235		if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_PADDINGTON)
236			bus_space_write_1(ca.ca_tag, bsh, 0x37, 0x03);
237	}
238
239	devhandle_t selfh = device_handle(self);
240	for (child = OF_child(node); child; child = OF_peer(child)) {
241		namelen = OF_getprop(child, "name", name, sizeof(name));
242		if (namelen < 0)
243			continue;
244		if (namelen >= sizeof(name))
245			continue;
246
247#ifdef OBIO_SPEED_CONTROL
248		if (strcmp(name, "gpio") == 0) {
249
250			obio_setup_gpios(sc, child);
251			continue;
252		}
253#endif
254
255		name[namelen] = 0;
256		ca.ca_name = name;
257		ca.ca_node = child;
258		ca.ca_tag = pa->pa_memt;
259
260		ca.ca_nreg = OF_getprop(child, "reg", reg, sizeof(reg));
261
262		if (strcmp(compat, "gatwick") != 0) {
263			ca.ca_nintr = OF_getprop(child, "AAPL,interrupts", intr,
264					sizeof(intr));
265			if (ca.ca_nintr == -1)
266				ca.ca_nintr = OF_getprop(child, "interrupts", intr,
267						sizeof(intr));
268		} else {
269			intr[0] = parent_intr;
270			ca.ca_nintr = parent_nintr;
271		}
272		ca.ca_reg = reg;
273		ca.ca_intr = intr;
274
275		config_found(self, &ca, obio_print,
276		    CFARGS(.devhandle = devhandle_from_of(selfh, child)));
277	}
278}
279
280static const char * const skiplist[] = {
281	"interrupt-controller",
282	"chrp,open-pic",
283	"open-pic",
284	"mpic",
285	"gpio",
286	"escc-legacy",
287	"timer",
288	"i2c",
289	"power-mgt",
290	"escc",
291	"battery",
292	"backlight"
293
294};
295
296#define N_LIST (sizeof(skiplist) / sizeof(skiplist[0]))
297
298int
299obio_print(void *aux, const char *obio)
300{
301	struct confargs *ca = aux;
302	int i;
303
304	for (i = 0; i < N_LIST; i++)
305		if (strcmp(ca->ca_name, skiplist[i]) == 0)
306			return QUIET;
307
308	if (obio)
309		aprint_normal("%s at %s", ca->ca_name, obio);
310
311	if (ca->ca_nreg > 0)
312		aprint_normal(" offset 0x%x", ca->ca_reg[0]);
313
314	return UNCONF;
315}
316
317void obio_write_4(int offset, uint32_t value)
318{
319	if (obio0 == NULL)
320		return;
321	bus_space_write_4(obio0->sc_tag, obio0->sc_bh, offset, value);
322}
323
324void obio_write_1(int offset, uint8_t value)
325{
326	if (obio0 == NULL)
327		return;
328	bus_space_write_1(obio0->sc_tag, obio0->sc_bh, offset, value);
329}
330
331uint32_t obio_read_4(int offset)
332{
333	if (obio0 == NULL)
334		return 0xffffffff;
335	return bus_space_read_4(obio0->sc_tag, obio0->sc_bh, offset);
336}
337
338uint8_t obio_read_1(int offset)
339{
340	if (obio0 == NULL)
341		return 0xff;
342	return bus_space_read_1(obio0->sc_tag, obio0->sc_bh, offset);
343}
344
345int
346obio_space_map(bus_addr_t addr, bus_size_t size, bus_space_handle_t *bh)
347{
348	if (obio0 == NULL)
349		return 0xff;
350	return bus_space_subregion(obio0->sc_tag, obio0->sc_bh,
351	    addr & 0xfffff, size, bh);
352}
353
354#ifdef OBIO_SPEED_CONTROL
355
356static void
357obio_setup_cpufreq(device_t dev)
358{
359	struct obio_softc *sc = device_private(dev);
360	int ret;
361
362	ret = cpufreq_register(&sc->sc_cf);
363	if (ret != 0)
364		aprint_error_dev(sc->sc_dev, "cpufreq_register() failed, error %d\n", ret);
365}
366
367static void
368obio_setup_gpios(struct obio_softc *sc, int node)
369{
370	uint32_t gpio_base, reg[6];
371	const struct sysctlnode *sysctl_node, *me, *freq;
372	struct cpufreq *cf = &sc->sc_cf;
373	char name[32];
374	int child, use_dfs, cpunode, hiclock;
375
376	if (! of_compatible(sc->sc_node, keylargo))
377		return;
378
379	if (OF_getprop(node, "reg", reg, sizeof(reg)) < 4)
380		return;
381
382	gpio_base = reg[0];
383	DPRINTF("gpio_base: %02x\n", gpio_base);
384
385	/* now look for voltage and bus speed gpios */
386	use_dfs = 0;
387	for (child = OF_child(node); child; child = OF_peer(child)) {
388
389		if (OF_getprop(child, "name", name, sizeof(name)) < 1)
390			continue;
391
392		if (OF_getprop(child, "reg", reg, sizeof(reg)) < 4)
393			continue;
394
395		/*
396		 * These register offsets either have to be added to the obio
397		 * base address or to the gpio base address. This differs
398		 * even in the same OF-tree! So we guess the offset is
399		 * based on obio when it is larger than the gpio_base.
400		 */
401		if (reg[0] >= gpio_base)
402			reg[0] -= gpio_base;
403
404		if (strcmp(name, "frequency-gpio") == 0) {
405			DPRINTF("found frequency_gpio at %02x\n", reg[0]);
406			sc->sc_busspeed = gpio_base + reg[0];
407		}
408		if (strcmp(name, "voltage-gpio") == 0) {
409			DPRINTF("found voltage_gpio at %02x\n", reg[0]);
410			sc->sc_voltage = gpio_base + reg[0];
411		}
412		if (strcmp(name, "cpu-vcore-select") == 0) {
413			DPRINTF("found cpu-vcore-select at %02x\n", reg[0]);
414			sc->sc_voltage = gpio_base + reg[0];
415			/* frequency gpio is not needed, we use cpu's DFS */
416			use_dfs = 1;
417		}
418	}
419
420	if ((sc->sc_voltage < 0) || (sc->sc_busspeed < 0 && !use_dfs))
421		return;
422
423	printf("%s: enabling Intrepid CPU speed control\n",
424	    device_xname(sc->sc_dev));
425
426	sc->sc_spd_lo = curcpu()->ci_khz / 1000;
427	hiclock = 0;
428	cpunode = OF_finddevice("/cpus/@0");
429	OF_getprop(cpunode, "clock-frequency", &hiclock, 4);
430	if (hiclock != 0)
431		sc->sc_spd_hi = (hiclock + 500000) / 1000000;
432	printf("hiclock: %d\n", sc->sc_spd_hi);
433	if (use_dfs) sc->sc_spd_lo = sc->sc_spd_hi / 2;
434
435	sysctl_node = NULL;
436
437	if (sysctl_createv(NULL, 0, NULL,
438	    &me,
439	    CTLFLAG_READWRITE, CTLTYPE_NODE, "cpu", NULL, NULL,
440	    0, NULL, 0, CTL_MACHDEP, CTL_CREATE, CTL_EOL) != 0)
441		printf("couldn't create 'cpu' node\n");
442
443	if (sysctl_createv(NULL, 0, NULL,
444	    &freq,
445	    CTLFLAG_READWRITE, CTLTYPE_NODE, "frequency", NULL, NULL,
446	    0, NULL, 0, CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL) != 0)
447		printf("couldn't create 'frequency' node\n");
448
449	if (sysctl_createv(NULL, 0, NULL,
450	    &sysctl_node,
451	    CTLFLAG_READWRITE | CTLFLAG_OWNDESC,
452	    CTLTYPE_INT, "target", "CPU speed", sysctl_cpuspeed_temp,
453	    0, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num,
454	    CTL_CREATE, CTL_EOL) == 0) {
455	} else
456		printf("couldn't create 'target' node\n");
457
458	if (sysctl_createv(NULL, 0, NULL,
459	    &sysctl_node,
460	    CTLFLAG_READWRITE,
461	    CTLTYPE_INT, "current", NULL, sysctl_cpuspeed_cur,
462	    1, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num,
463	    CTL_CREATE, CTL_EOL) == 0) {
464	} else
465		printf("couldn't create 'current' node\n");
466
467	if (sysctl_createv(NULL, 0, NULL,
468	    &sysctl_node,
469	    CTLFLAG_READWRITE,
470	    CTLTYPE_STRING, "available", NULL, sysctl_cpuspeed_available,
471	    2, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num,
472	    CTL_CREATE, CTL_EOL) == 0) {
473	} else
474		printf("couldn't create 'available' node\n");
475	printf("speed: %d\n", curcpu()->ci_khz);
476
477	/* support cpufreq */
478	snprintf(cf->cf_name, CPUFREQ_NAME_MAX, "Intrepid");
479	cf->cf_state[0].cfs_freq = sc->sc_spd_hi;
480	cf->cf_state[1].cfs_freq = sc->sc_spd_lo;
481	cf->cf_state_count = 2;
482	cf->cf_mp = FALSE;
483	cf->cf_cookie = sc;
484	cf->cf_get_freq = obio_get_freq;
485	cf->cf_set_freq = obio_set_freq;
486	/*
487	 * XXX
488	 * cpufreq_register() calls xc_broadcast() which relies on kthreads
489	 * running so we need to postpone it
490	 */
491	config_interrupts(sc->sc_dev, obio_setup_cpufreq);
492}
493
494static void
495obio_set_cpu_speed(struct obio_softc *sc, int fast)
496{
497
498	if (sc->sc_voltage < 0)
499		return;
500
501	if (sc->sc_busspeed >= 0) {
502		/* set voltage and speed via gpio */
503		if (fast) {
504			bus_space_write_1(sc->sc_tag, sc->sc_bh,
505			    sc->sc_voltage, 5);
506			bus_space_write_1(sc->sc_tag, sc->sc_bh,
507			    sc->sc_busspeed, 5);
508		} else {
509			bus_space_write_1(sc->sc_tag, sc->sc_bh,
510			    sc->sc_busspeed, 4);
511			bus_space_write_1(sc->sc_tag, sc->sc_bh,
512			    sc->sc_voltage, 4);
513		}
514	}
515	else {
516		/* set voltage via gpio and speed via the 7447A's DFS bit */
517		if (fast) {
518			bus_space_write_1(sc->sc_tag, sc->sc_bh,
519			    sc->sc_voltage, 5);
520			DELAY(1000);
521		}
522
523		/* set DFS for all cpus */
524		cpu_set_dfs(fast ? 1 : 2);
525		DELAY(100);
526
527		if (!fast) {
528			bus_space_write_1(sc->sc_tag, sc->sc_bh,
529			    sc->sc_voltage, 4);
530			DELAY(1000);
531		}
532	}
533}
534
535static int
536obio_get_cpu_speed(struct obio_softc *sc)
537{
538
539	if (sc->sc_voltage < 0)
540		return 0;
541
542	if (sc->sc_busspeed >= 0) {
543		if (bus_space_read_1(sc->sc_tag, sc->sc_bh, sc->sc_busspeed)
544		    & 1)
545			return 1;
546	}
547	else
548		return cpu_get_dfs() == 1;
549
550	return 0;
551}
552
553static void
554obio_get_freq(void *cookie, void *spd)
555{
556	struct obio_softc *sc = cookie;
557	uint32_t *freq;
558
559	freq = spd;
560	if (obio_get_cpu_speed(sc) == 0) {
561		*freq = sc->sc_spd_lo;
562	} else
563		*freq = sc->sc_spd_hi;
564}
565
566static void
567obio_set_freq(void *cookie, void *spd)
568{
569	struct obio_softc *sc = cookie;
570	uint32_t *freq;
571
572	freq = spd;
573	if (*freq == sc->sc_spd_lo) {
574		obio_set_cpu_speed(sc, 0);
575	} else if (*freq == sc->sc_spd_hi) {
576		obio_set_cpu_speed(sc, 1);
577	} else
578		aprint_error_dev(sc->sc_dev, "%s(%d) bogus CPU speed\n", __func__, *freq);
579}
580
581static int
582sysctl_cpuspeed_temp(SYSCTLFN_ARGS)
583{
584	struct sysctlnode node = *rnode;
585	struct obio_softc *sc = node.sysctl_data;
586	int speed, mhz;
587
588	speed = obio_get_cpu_speed(sc);
589	switch (speed) {
590		case 0:
591			mhz = sc->sc_spd_lo;
592			break;
593		case 1:
594			mhz = sc->sc_spd_hi;
595			break;
596		default:
597			speed = -1;
598	}
599	node.sysctl_data = &mhz;
600	if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) {
601		int new_reg;
602
603		new_reg = *(int *)node.sysctl_data;
604		if (new_reg == sc->sc_spd_lo) {
605			obio_set_cpu_speed(sc, 0);
606		} else if (new_reg == sc->sc_spd_hi) {
607			obio_set_cpu_speed(sc, 1);
608		} else {
609			printf("%s: new_reg %d\n", __func__, new_reg);
610			return EINVAL;
611		}
612		return 0;
613	}
614	return EINVAL;
615}
616
617static int
618sysctl_cpuspeed_cur(SYSCTLFN_ARGS)
619{
620	struct sysctlnode node = *rnode;
621	struct obio_softc *sc = node.sysctl_data;
622	int speed, mhz;
623
624	speed = obio_get_cpu_speed(sc);
625	switch (speed) {
626		case 0:
627			mhz = sc->sc_spd_lo;
628			break;
629		case 1:
630			mhz = sc->sc_spd_hi;
631			break;
632		default:
633			speed = -1;
634	}
635	node.sysctl_data = &mhz;
636	return sysctl_lookup(SYSCTLFN_CALL(&node));
637}
638
639static int
640sysctl_cpuspeed_available(SYSCTLFN_ARGS)
641{
642	struct sysctlnode node = *rnode;
643	struct obio_softc *sc = node.sysctl_data;
644	char buf[128];
645
646	snprintf(buf, 128, "%d %d", sc->sc_spd_lo, sc->sc_spd_hi);
647	node.sysctl_data = buf;
648	return(sysctl_lookup(SYSCTLFN_CALL(&node)));
649}
650
651SYSCTL_SETUP(sysctl_ams_setup, "sysctl obio subtree setup")
652{
653
654	sysctl_createv(NULL, 0, NULL, NULL,
655		       CTLFLAG_PERMANENT,
656		       CTLTYPE_NODE, "machdep", NULL,
657		       NULL, 0, NULL, 0,
658		       CTL_MACHDEP, CTL_EOL);
659}
660
661#endif /* OBIO_SPEEDCONTROL */
662