shithub: riscv

Download patch

ref: a78b71b143240fa1dad019f5c655a03c2d2400d9
parent: 5e15db8fa31dd68fee22f260ae797a38ccaa4070
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Sat Aug 20 21:30:03 EDT 2022

move and rename MNT Reform 2 support utilies, cat manpages into one

Aux/imx8pm and aux/wm8960 had pretty cryptic names and it made
more sense to use a reform/pm and reform/audio naming, accordingly.

Instead of having special mount points /mnt/pm and /mnt/wm8960,
/dev is used directly, removing the need to do any manual work -
stats(1) will continue showing the CPU temperature, and zuke(1)
will still be able to control the volume, etc.

Brightness controls were changed to a better interface -
/dev/light, where each line contains a "a_thing its_light_value".
This way more parts can be controlled. Right now it's only "lcd",
but later it might be "kbd" and "trackball" as well.

Example of lib/profile:

reform/audio
echo master 80 > /dev/volume

reform/pm
echo lcd 100 > /dev/light

diff: cannot open b/sys/src/cmd/reform//null: file does not exist: 'b/sys/src/cmd/reform//null'
--- /dev/null
+++ b/sys/man/1/reform
@@ -1,0 +1,115 @@
+.TH REFORM 1
+.SH NAME
+audio,
+pm
+- MNT Reform 2 support utilities
+.SH SYNOPSIS
+.B reform/audio
+[
+.B -1
+]
+[
+.B -D
+]
+[
+.B -m
+.I mountpoint
+]
+[
+.B -s
+.I service
+]
+.PP
+.B reform/pm
+[
+.B -D
+]
+[
+.B -m
+.I mountpoint
+]
+[
+.B -s
+.I service
+]
+.SH DESCRIPTION
+These programs provide support for certain functions of MNT Reform 2
+computing device make controlling file systems available under
+.BR /dev .
+.PP
+.SS Audio
+.I audio
+initializes the DAC (Digital-to-Analog Converter) on the platform and
+provides a standard \fIaudio\fR(3) interface to control volume and
+other parameters.
+With
+.I -1
+only the initialization is performed and the program exits
+immediately.
+.PP
+The following files are provided by the program:
+.TP
+.B audioctl
+Shows the current status
+.I (on
+or
+.I off )
+of the three "outputs" -
+.BR master ,
+.BR hp
+and
+.BR spk .
+Each can be enabled, disabled or toggled, by writing a single line to
+the same file, consisting of the output name and the desired action -
+.I on ,
+.I off
+or
+.I toggle ,
+accordingly.
+.IP
+DAC can be reinitialized by writing a single
+.BR reset .
+.TP
+.B volume
+Provides an interface for volume control (see \fIaudio\fR(3)).  For
+ease of use,
+.B volume
+supports relative adjustments by prefixing a number with a sign.
+.IP
+.IP
+Enhanced stereo separation can be enabled by writing
+.BR 3d ,
+followed by desired percentage of the effect.
+.SS Power and monitoring
+.I pm
+presents a file system consisting of the following files:
+.TP
+.B light
+Provides a way to control the backlight of the built-in LCD by
+writing \fIlcd [-+]N\fR,
+where
+.I N
+is expressed in percentage, either as an absolute value (0-100) or
+relative to the current brightness - by prefixing with a sign.
+Reading
+.B light
+returns the current brightness.
+.TP
+.B cputemp
+Exposes the current temperature reading of the CPU.
+.SH SOURCE
+.B /sys/src/cmd/reform
+.SH SEE ALSO
+.IR audio (3)
+.SH HISTORY
+MNT Reform 2 support first appeared in 9front (August, 2022).
+.SH BUGS
+Only 44100Hz (default) and 48000Hz sample rates are supported with
+.IR audio ,
+recording is not implemented.
+.PP
+.I Light
+was chosen as a shorter alternative to
+.IR brightness .
+In the future it might support controlling keyboard and trackball
+light levels.
--- a/sys/man/8/imx8pm
+++ /dev/null
@@ -1,58 +1,0 @@
-.TH IMX8PM 8
-.SH NAME
-imx8pm \- power management for MNT Reform 2
-.SH SYNOPSIS
-.B aux/imx8pm
-[
-.B -D
-]
-[
-.B -m
-.I mountpoint
-]
-[
-.B -s
-.I service
-]
-.nf
-.sp 0.3v
-.B /mnt/pm/cputemp
-.B /mnt/pm/ctl
-.fi
-.SH DESCRIPTION
-.I Aux/imx8pm
-presents at
-.I mountpoint
-(default
-.BR /mnt/pm )
-an interface to miscellaneous functions of IMX8MQ. If a
-.I service
-is specified, the interface will be posted at
-.BI /srv/ service 
-as well.
-.PP
-.B cputemp
-Exposes the current temperature reading of the CPU.
-.PP
-.B ctl
-Provides a way to control the brightness of the built-in LCD by
-writing
-.IP
-.I brightness [-+]N
-.PP
-.B N
-is expressed in percentage, either as an absolute value (0-100) or
-relative to the current brightness - by prefixing with a sign.
-.PP
-Reading
-.B ctl
-returns the current brightness.
-.SH SOURCE
-.B /sys/src/cmd/aux/imx8pm.c
-.SH SEE ALSO
-.IR wm8960 (8)
-.SH BUGS
-Absolutely yes.
-.SH HISTORY
-.I Acpi
-first appeared in 9front (August, 2022).
--- a/sys/man/8/wm8960
+++ /dev/null
@@ -1,83 +1,0 @@
-.TH WM8960 8
-.SH NAME
-wm8960 \- audio controls for MNT Reform 2
-.SH SYNOPSIS
-.B aux/wm8960
-[
-.B -D
-]
-[
-.B -1
-]
-[
-.B -m
-.I mountpoint
-]
-[
-.B -s
-.I service
-]
-.nf
-.sp 0.3v
-.B /mnt/wm8960/audioctl
-.B /mnt/wm8960/volume
-.fi
-.SH DESCRIPTION
-.I Aux/imx8pm
-presents at
-.I mountpoint
-(default
-.BR /mnt/wm8960 )
-an interface to control the DAC (Digital-to-Analog Converter) found on
-MNT Reform 2.  If a
-.I service
-is specified, the interface will be posted at
-.BI /srv/ service 
-as well. In order to initialize the DAC and immediately exit, without
-running a file system, option
-.I -1
-can be used.
-.PP
-The directory contains the following files.
-.TP
-.B audioctl
-Shows the current status
-.I (on
-or
-.I off )
-of the three "outputs" -
-.BR master ,
-.BR hp
-and
-.BR spk .
-Each can be enabled, disabled or toggled, by writing a single line
-to the same file, consisting of the output name and the desired action -
-.I on ,
-.I off
-or
-.I toggle ,
-accordingly.
-.IP
-DAC can be reinitialized by writing a single
-.BR reset .
-.TP
-.B volume
-Provides an interface for volume control (see \fIaudio\fR(3)). For ease of use,
-.B volume
-supports relative adjustments by prefixing a number with a sign.
-.IP
-Only
-44100Hz (default) and 48000Hz sample rates are supported.
-.IP
-Enhanced stereo separation can be enabled by writing
-.BR 3d ,
-followed by desired percentage of the effect.
-.SH SOURCE
-.B /sys/src/cmd/aux/wm8960.c
-.SH SEE ALSO
-.IR imx8pm (8)
-.SH BUGS
-Absolutely yes.
-.SH HISTORY
-.I Wm8960
-first appeared in 9front (August, 2022).
--- a/sys/src/cmd/aux/imx8pm.c
+++ /dev/null
@@ -1,247 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <fcall.h>
-#include <thread.h>
-#include <9p.h>
-
-enum
-{
-	Mhz = 1000*1000,
-	Pwmsrcclk = 25*Mhz,
-
-	Ctl = 1,
-	Temp,
-
-	PWMSAR = 0x0c/4,
-	PWMPR = 0x10/4,
-
-	TMUTMR = 0x00/4,
-		TMR_ME = 1<<31,
-		TMR_ALPF_SHIFT = 26,
-		TMR_MSITE_SHIFT = 13,
-	TMUTSR = 0x04/4,
-		TSR_MIE = 1<<30,
-		TSR_ORL = 1<<29,
-		TSR_ORH = 1<<28,
-	TMUTMTMIR = 0x08/4,
-	TMUTIER = 0x20/4,
-	TMUTIDR = 0x24/4,
-		TIDR_MASK = 0xe0000000,
-	TMUTISCR = 0x28/4,
-	TMUTICSCR = 0x2c/4,
-	TMUTTCFGR = 0x80/4,
-	TMUTSCFGR = 0x84/4,
-	TMUTRITSR0 = 0x100/4,
-	TMUTRITSR1 = 0x110/4,
-	TMUTRITSR2 = 0x120/4,
-	TMUTTR0CR = 0xf10/4,
-	TMUTTR1CR = 0xf14/4,
-	TMUTTR2CR = 0xf18/4,
-	TMUTTR3CR = 0xf1c/4,
-		CR_CAL_PTR_SHIFT = 16,
-	
-};
-
-static u32int *pwm2, *tmu;
-static char *uid = "mntpm";
-
-static void
-wr(u32int *base, int reg, u32int v)
-{
-	//fprint(2, "[0%x] ← 0x%ux\n", reg*4, v);
-	if(base != nil)
-		base[reg] = v;
-}
-
-static u32int
-rd(u32int *base, int reg)
-{
-	return base != nil ? base[reg] : -1;
-}
-
-static void
-setbrightness(int p)
-{
-	u32int v;
-
-	if(p < 0)
-		p = 0;
-	if(p > 100)
-		p = 100;
-
-	v = Pwmsrcclk / rd(pwm2, PWMSAR);
-	wr(pwm2, PWMPR, (Pwmsrcclk/(v*p/100))-2);
-}
-
-static int
-getbrightness(void)
-{
-	u32int m, v;
-
-	m = Pwmsrcclk / rd(pwm2, PWMSAR);
-	v = Pwmsrcclk / (rd(pwm2, PWMPR)+2);
-	return v*100/m;
-}
-
-static int
-getcputemp(int c[3])
-{
-	int i, r[] = {TMUTRITSR0, TMUTRITSR1, TMUTRITSR2};
-	u32int s;
-
-	s = rd(tmu, TMUTSR);
-	if(s & TSR_MIE){
-		werrstr("monitoring interval exceeded");
-		return -1;
-	}
-	if(s & (TSR_ORL|TSR_ORH)){
-		werrstr("out of range");
-		return -1;
-	}
-
-	c[0] = c[1] = c[2] = 0;
-	for(;;){
-		for(i = 0; i < 3; i++)
-			if(c[i] >= 0)
-				c[i] = rd(tmu, r[i]);
-		if(c[0] < 0 && c[1] < 0 && c[2] < 0)
-			break;
-		sleep(10);
-	}
-	c[0] &= 0xff;
-	c[1] &= 0xff;
-	c[2] &= 0xff;
-	return 0;
-}
-
-static void
-tmuinit(void)
-{
-	/* without proper calibration data sensing is useless */
-	static u8int cfg[4][12] = {
-		{0x23, 0x29, 0x2f, 0x35, 0x3d, 0x43, 0x4b, 0x51, 0x57, 0x5f, 0x67, 0x6f},
-		{0x1b, 0x23, 0x2b, 0x33, 0x3b, 0x43, 0x4b, 0x55, 0x5d, 0x67, 0x70, 0},
-		{0x17, 0x23, 0x2d, 0x37, 0x41, 0x4b, 0x57, 0x63, 0x6f, 0},
-		{0x15, 0x21, 0x2d, 0x39, 0x45, 0x53, 0x5f, 0x71, 0},
-	};
-	int i, j;
-
-	wr(tmu, TMUTMR, 0); /* disable */
-	wr(tmu, TMUTIER, 0); /* disable all interrupts */
-	wr(tmu, TMUTMTMIR, 0xf); /* no monitoring interval */
-
-	/* configure default ranges */
-	wr(tmu, TMUTTR0CR, 11<<CR_CAL_PTR_SHIFT | 0);
-	wr(tmu, TMUTTR1CR, 10<<CR_CAL_PTR_SHIFT | 38);
-	wr(tmu, TMUTTR2CR, 8<<CR_CAL_PTR_SHIFT | 72);
-	wr(tmu, TMUTTR3CR, 7<<CR_CAL_PTR_SHIFT | 97);
-
-	/* calibration data */
-	for(i = 0; i < 4; i++){
-		for(j = 0; j < 12 && cfg[i][j] != 0; j++){
-			wr(tmu, TMUTTCFGR, i<<16|j);
-			wr(tmu, TMUTSCFGR, cfg[i][j]);
-		}
-	}
-
-	/* enable: all sites, ALPF 11=0.125 */
-	wr(tmu, TMUTMR, TMR_ME | 3<<TMR_ALPF_SHIFT | 7<<TMR_MSITE_SHIFT);
-}
-
-static void
-fsread(Req *r)
-{
-	char msg[256];
-	int c[3];
-
-	msg[0] = 0;
-	if(r->ifcall.offset == 0){
-		if(r->fid->file->aux == (void*)Ctl)
-			snprint(msg, sizeof(msg), "brightness %d\n", getbrightness());
-		else if(r->fid->file->aux == (void*)Temp){
-			if(getcputemp(c) == 0)
-				snprint(msg, sizeof(msg), "%d.0\n", c[0]);
-			else
-				snprint(msg, sizeof(msg), "%r\n");
-		}
-	}
-
-	readstr(r, msg);
-	respond(r, nil);
-}
-
-static void
-fswrite(Req *r)
-{
-	char msg[256], *f[4];
-	int nf, v;
-
-	if(r->fid->file->aux == (void*)Ctl){
-		snprint(msg, sizeof(msg), "%.*s",
-			utfnlen((char*)r->ifcall.data, r->ifcall.count), (char*)r->ifcall.data);
-		nf = tokenize(msg, f, nelem(f));
-		if(nf < 2){
-			respond(r, "invalid ctl message");
-			return;
-		}
-		if(strcmp(f[0], "brightness") == 0){
-			v = atoi(f[1]);
-			if(*f[1] == '+' || *f[1] == '-')
-				v += getbrightness();
-			setbrightness(v);
-		}
-	}
-
-	r->ofcall.count = r->ifcall.count;
-	respond(r, nil);
-}
-
-static Srv fs = {
-	.read = fsread,
-	.write = fswrite,
-};
-
-static void
-usage(void)
-{
-	fprint(2, "usage: aux/imx8pm [-D] [-m /mnt/pm] [-s service]\n");
-	exits("usage");
-}
-
-void
-main(int argc, char **argv)
-{
-	char *mtpt, *srv;
-
-	mtpt = "/mnt/pm";
-	srv = nil;
-	ARGBEGIN{
-	case 'D':
-		chatty9p = 1;
-		break;
-	case 'm':
-		mtpt = EARGF(usage());
-		break;
-	case 's':
-		srv = EARGF(usage());
-		break;
-	default:
-		usage();
-	}ARGEND
-
-	fs.tree = alloctree(uid, uid, DMDIR|0555, nil);
-	createfile(fs.tree->root, "ctl", uid, 0666, (void*)Ctl);
-
-	if((tmu = segattach(0, "tmu", 0, 0xf20)) == (void*)-1)
-		tmu = nil;
-	else{
-		createfile(fs.tree->root, "cputemp", uid, 0444, (void*)Temp);
-		tmuinit();
-	}
-	if((pwm2 = segattach(0, "pwm2", 0, 0x18)) == (void*)-1)
-		pwm2 = nil;
-
-	postmountsrv(&fs, srv, mtpt, MREPL);
-
-	exits(nil);
-}
--- a/sys/src/cmd/aux/mkfile
+++ b/sys/src/cmd/aux/mkfile
@@ -22,7 +22,6 @@
 	getflags\
 	icanhasmsi\
 	icanhasvmx\
-	imx8pm\
 	lines\
 	listen\
 	listen1\
@@ -50,7 +49,6 @@
 	write\
 	wacom\
 	wikifmt\
-	wm8960\
 	wpa\
 	zerotrunc\
 
--- a/sys/src/cmd/aux/wm8960.c
+++ /dev/null
@@ -1,370 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <fcall.h>
-#include <thread.h>
-#include <9p.h>
-
-#define MIN(a,b) (a<b?a:b)
-#define MAX(a,b) (a>b?a:b)
-#define CLAMP(x,a,b) MAX(a,MIN(x,b))
-
-typedef struct Out Out;
-
-enum
-{
-	Dac,
-	Hp,
-	Spk,
-	Nout,
-
-	Ctl = 1,
-	Vol,
-};
-
-struct Out
-{
-	char *name;
-	int volreg;
-	int volmax;
-	int togglemask;
-	void (*toggle)(Out *o, int on);
-	int on;
-	int vol[2];
-};
-
-static char *uid = "audio";
-static int data;
-static int reg1a;
-static int rate = 44100;
-static int ⅓d;
-
-static void
-wr(int a, int v)
-{
-	u8int c;
-
-	//fprint(2, "[0x%x] ← 0x%ux\n", a, v & 0x1ff);
-	c = v & 0xff;
-	if(pwrite(data, &c, 1, a<<1 | ((v>>8)&1)) < 1)
-		fprint(2, "reg %x write failed: %r\n", a);
-}
-
-static void
-classdspk(Out *, int on)
-{
-	wr(0x31, (on ? 3 : 0)<<6 | 0x37); /* class D SPK out */
-}
-
-static void
-toggle(Out *o, int on)
-{
-	if(on)
-		reg1a |= o->togglemask;
-	else
-		reg1a &= ~o->togglemask;
-	wr(0x1a, reg1a);
-	if(o->toggle != nil)
-		o->toggle(o, on);
-	o->on = on;
-}
-
-static Out out[Nout] =
-{
-	[Dac] = {"master", 0x0a, 0xf9, 3<<7, nil, 0},
-	[Hp] = {"hp", 0x02, 0x7f, 3<<5, nil, 0},
-	[Spk] = {"spk", 0x28, 0x7f, 3<<3, classdspk, 0},
-};
-
-static void
-setvol(Out *o, int l, int r)
-{
-	int zc;
-
-	o->vol[0] = l = CLAMP(l, 0, 100);
-	o->vol[1] = r = CLAMP(r, 0, 100);
-	if(l > 0)
-		l += o->volmax - 100;
-	if(r > 0)
-		r += o->volmax - 100;
-
-	zc = o->volmax < 0x80;
-	wr(o->volreg+0, 0<<8 | zc<<7 | l);
-	wr(o->volreg+1, 1<<8 | zc<<7 | r);
-}
-
-static void
-set3d(int x)
-{
-	⅓d = CLAMP(x, 0, 100);
-	x = (⅓d+5)/7;
-	wr(0x10, x<<1 | (x ? 1 : 0)<<0);
-}
-
-static int
-setrate(int s)
-{
-	u32int k;
-
-	if(s != 44100 && s != 48000)
-		return -1;
-
-	/*
-	 * getting DAC ready for s16c2r44100:
-	 *
-	 * mclk₀ = 25Mhz (set in sai)
-	 * pllprescale = /2 → *actual* mclk₁ is 25/2 = 12.5Mhz
-	 * sysclk = 44.1kHz*256 = 11.2896Mhz
-	 *   → dacdiv = /(1*256) = sysclk/(1*256) = 44.1kHz
-	 * f₂ = 4*2*sysclk = 90.3168Mhz
-	 *
-	 * PLL freq ration:
-	 *   R = f₂/mclk₁
-	 *   N = int(R) = 7
-	 *   K = 2²⁴*(R-N) = 3780644.9623
-	 *
-	 * dacdiv = /(1*256) → DAC at max rate
-	 *  → pick bclk rate 1.4112Mhz (sysclk/8)
-	 *  → bclkdiv = /8
-	 *
-	 * class D clk needs to be ~768kHz (700-800)
-	 *  → sysclk/768000 = 14
-	 *  → dclkdiv = /16 → dclk = 705.6kHz
-	 */
-	wr(0x1a, reg1a = reg1a & ~(1<<0)); /* disable pll */
-
-	wr(0x04,
-		0<<3 | /* dacdiv → sysclk/(1*256) = 44100 */
-		2<<1 | /* sysclkdiv → /2 */
-		1<<0 | /* clksel → pll output */
-		0
-	);
-	wr(0x34,
-		1<<5 | /* enable fractional mode */
-		1<<4 | /* pllprescale */
-		7<<0 | /* N */
-		0
-	);
-	k = s == 44100 ? 3780645 : 14500883; /* K */
-	wr(0x35, (k>>16) & 0xff);
-	wr(0x36, (k>>8) & 0xff);
-	wr(0x37, k & 0xff);
-
-	wr(0x08, 7<<6 | 7<<0); /* dclkdiv → sysclk/16; bclkdiv → sysclk/8 */
-	wr(0x1a, reg1a = reg1a | 1<<0); /* enable pll */
-
-	rate = s;
-
-	return 0;
-}
-
-static void
-reset(void)
-{
-	int i;
-
-	for(i = 0; i < Nout; i++){
-		out[i].vol[0] = -1;
-		out[i].vol[1] = -1;
-	}
-
-	toggle(out+Dac, 0);
-	wr(0x1c, 1<<7 | 1<<4 | 1<<3 | 1<<2); /* Vmid/r bias; Vgs/r on; Vmid soft start */
-	wr(0x19, 0<<7); /* Vmid off, Vref off */
-	sleep(500);
-	wr(0x0f, 0); /* reset registers to default */
-
-	setrate(rate);
-	set3d(⅓d);
-
-	wr(0x07, 1<<6 | 2); /* master mode; i²s, 16-bit words */
-
-	wr(0x17, 1<<8 | 3<<6 | 0<<1); /* thermal shutdown on; avdd=3.3v; slow clock on */
-
-	wr(0x06, 1<<3 | 1<<2); /* ramp up DAC volume slowly */
-	wr(0x2f, 3<<2); /* output mixer on */
-	wr(0x22, 1<<8); /* L DAC to mixer */
-	wr(0x25, 1<<8); /* R DAC to mixer */
-
-	wr(0x17, 1<<8 | 3<<6 | 1<<0); /* thermal shutdown on; avdd=3.3v; slow clock on */
-	wr(0x1c, 1<<7 | 1<<4 | 1<<3 | 1<<2); /* Vmid/r bias; Vgs/r on; Vmid soft start */
-	wr(0x19, 1<<7); /* start Vmid (playback) */
-	sleep(650);
-	wr(0x1c, 1<<3); /* done with anti-pop */
-	wr(0x19, 1<<7 | 1<<6); /* Vref on */
-
-	wr(0x09, 1<<6); /* adclrc → gpio (for jack detect output) */
-	wr(0x30, 3<<4 | 2<<2 | 1<<1); /* JD2 jack detect in; Tsense on */
-	wr(0x1b, 1<<3); /* HP_[LR] responsive to jack detect */
-	wr(0x18, 1<<6); /* HP switch on; high = HP */
-
-	/* turn on all outputs */
-	toggle(out+Hp, 1);
-	toggle(out+Spk, 1);
-	toggle(out+Dac, 1);
-
-	/* sensible defaults */
-	setvol(out+Spk, 100, 100);
-	setvol(out+Hp, 75, 75);
-	setvol(out+Dac, 80, 80);
-
-	/*
-	 * Jack detect becomes extremely unstable when playing on spk and the
-	 * volume is very high - DAC start to switch back and forth between two
-	 * outputs.  Solve this by always attenuating by -6dB and somewhat limiting
-	 * the volume on DAC ("master") - to 0xf9 instead of 0xff (max).
-	 */
-	wr(0x05, 1<<7 | 0<<3); /* unmute DAC */
-}
-
-static void
-fsread(Req *r)
-{
-	char msg[256], *s, *e;
-	Out *o;
-	int i;
-
-	s = msg;
-	e = msg+sizeof(msg);
-	*s = 0;
-	if(r->fid->file->aux == (void*)Ctl){
-		for(i = 0, o = out; i < Nout; i++, o++)
-			s = seprint(s, e, "%s %s\n", o->name, o->on ? "on" : "off");
-	}else if(r->fid->file->aux == (void*)Vol){
-		for(i = 0, o = out; i < Nout; i++, o++)
-			s = seprint(s, e, "%s %d %d\n", o->name, o->vol[0], o->vol[1]);
-		s = seprint(s, e, "speed %d\n", rate);
-	}
-	seprint(s, e, "3d %d\n", ⅓d);
-
-	readstr(r, msg);
-	respond(r, nil);
-}
-
-static int
-setoradd(int x, char *s)
-{
-	int d;
-
-	d = atoi(s);
-	if(*s == '+' || *s == '-')
-		return x + d;
-
-	return d;
-}
-
-static void
-fswrite(Req *r)
-{
-	int nf, on, i, vl, vr;
-	char msg[256], *f[4];
-	Out *o;
-
-	snprint(msg, sizeof(msg), "%.*s",
-		utfnlen((char*)r->ifcall.data, r->ifcall.count), (char*)r->ifcall.data);
-	nf = tokenize(msg, f, nelem(f));
-	if(nf == 1 && strcmp(f[0], "reset") == 0){
-		reset();
-		goto Done;
-	}else if(nf == 2 && strcmp(f[0], "speed") == 0){
-		if(setrate(atoi(f[1])) != 0){
-			respond(r, "not supported");
-			return;
-		}
-		goto Done;
-	}else if(nf == 2 && strcmp(f[0], "3d") == 0){
-		set3d(atoi(f[1]));
-		goto Done;
-	}
-	if(nf < 2){
-Emsg:
-		respond(r, "invalid ctl message");
-		return;
-	}
-
-	for(i = 0, o = out; i < Nout && strcmp(f[0], o->name) != 0; i++, o++)
-		;
-	if(i >= Nout)
-		goto Emsg;
-
-	if(r->fid->file->aux == (void*)Ctl){
-		if(nf != 2)
-			goto Emsg;
-		if(strcmp(f[1], "on") == 0)
-			on = 1;
-		else if(strcmp(f[1], "off") == 0)
-			on = 0;
-		else if(strcmp(f[1], "toggle") == 0)
-			on = !o->on;
-		else
-			goto Emsg;
-		toggle(o, on);
-	}else if(r->fid->file->aux == (void*)Vol){
-		vl = setoradd(o->vol[0], f[1]);
-		vr = setoradd(o->vol[1], nf < 3 ? f[1] : f[2]);
-		setvol(o, vl, vr);
-	}
-
-Done:
-	r->ofcall.count = r->ifcall.count;
-	respond(r, nil);
-}
-
-static Srv fs = {
-	.read = fsread,
-	.write = fswrite,
-};
-
-static void
-usage(void)
-{
-	fprint(2, "usage: aux/wm8960 [-1] [-D] [-m /mnt/pm] [-s service]\n");
-	exits("usage");
-}
-
-void
-main(int argc, char **argv)
-{
-	char *mtpt, *srv;
-	int ctl, oneshot;
-
-	mtpt = "/mnt/wm8960";
-	srv = nil;
-	oneshot = 0;
-	ARGBEGIN{
-	case '1':
-		oneshot = 1;
-		break;
-	case 'D':
-		chatty9p = 1;
-		break;
-	case 'm':
-		mtpt = EARGF(usage());
-		break;
-	case 's':
-		srv = EARGF(usage());
-		break;
-	default:
-		usage();
-	}ARGEND
-
-	if((data = open("#J/i2c3/i2c.1a.data", OWRITE)) < 0)
-		sysfatal("i2c data: %r");
-	if((ctl = open("#J/i2c3/i2c.1a.ctl", OWRITE)) < 0)
-		sysfatal("i2c ctl: %r");
-	fprint(ctl, "subaddress 1\n");
-	fprint(ctl, "size %d\n", 0x38<<1);
-	close(ctl);
-
-	reset();
-
-	if(oneshot)
-		exits(nil);
-
-	fs.tree = alloctree(uid, uid, DMDIR|0555, nil);
-	createfile(fs.tree->root, "audioctl", uid, 0666, (void*)Ctl);
-	createfile(fs.tree->root, "volume", uid, 0666, (void*)Vol);
-
-	postmountsrv(&fs, srv, mtpt, MREPL);
-
-	exits(nil);
-}
--- /dev/null
+++ b/sys/src/cmd/reform/audio.c
@@ -1,0 +1,370 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+#define MIN(a,b) (a<b?a:b)
+#define MAX(a,b) (a>b?a:b)
+#define CLAMP(x,a,b) MAX(a,MIN(x,b))
+
+typedef struct Out Out;
+
+enum
+{
+	Dac,
+	Hp,
+	Spk,
+	Nout,
+
+	Ctl = 1,
+	Vol,
+};
+
+struct Out
+{
+	char *name;
+	int volreg;
+	int volmax;
+	int togglemask;
+	void (*toggle)(Out *o, int on);
+	int on;
+	int vol[2];
+};
+
+static char *uid = "audio";
+static int data;
+static int reg1a;
+static int rate = 44100;
+static int ⅓d;
+
+static void
+wr(int a, int v)
+{
+	u8int c;
+
+	//fprint(2, "[0x%x] ← 0x%ux\n", a, v & 0x1ff);
+	c = v & 0xff;
+	if(pwrite(data, &c, 1, a<<1 | ((v>>8)&1)) < 1)
+		fprint(2, "reg %x write failed: %r\n", a);
+}
+
+static void
+classdspk(Out *, int on)
+{
+	wr(0x31, (on ? 3 : 0)<<6 | 0x37); /* class D SPK out */
+}
+
+static void
+toggle(Out *o, int on)
+{
+	if(on)
+		reg1a |= o->togglemask;
+	else
+		reg1a &= ~o->togglemask;
+	wr(0x1a, reg1a);
+	if(o->toggle != nil)
+		o->toggle(o, on);
+	o->on = on;
+}
+
+static Out out[Nout] =
+{
+	[Dac] = {"master", 0x0a, 0xf9, 3<<7, nil, 0},
+	[Hp] = {"hp", 0x02, 0x7f, 3<<5, nil, 0},
+	[Spk] = {"spk", 0x28, 0x7f, 3<<3, classdspk, 0},
+};
+
+static void
+setvol(Out *o, int l, int r)
+{
+	int zc;
+
+	o->vol[0] = l = CLAMP(l, 0, 100);
+	o->vol[1] = r = CLAMP(r, 0, 100);
+	if(l > 0)
+		l += o->volmax - 100;
+	if(r > 0)
+		r += o->volmax - 100;
+
+	zc = o->volmax < 0x80;
+	wr(o->volreg+0, 0<<8 | zc<<7 | l);
+	wr(o->volreg+1, 1<<8 | zc<<7 | r);
+}
+
+static void
+set3d(int x)
+{
+	⅓d = CLAMP(x, 0, 100);
+	x = (⅓d+5)/7;
+	wr(0x10, x<<1 | (x ? 1 : 0)<<0);
+}
+
+static int
+setrate(int s)
+{
+	u32int k;
+
+	if(s != 44100 && s != 48000)
+		return -1;
+
+	/*
+	 * getting DAC ready for s16c2r44100:
+	 *
+	 * mclk₀ = 25Mhz (set in sai)
+	 * pllprescale = /2 → *actual* mclk₁ is 25/2 = 12.5Mhz
+	 * sysclk = 44.1kHz*256 = 11.2896Mhz
+	 *   → dacdiv = /(1*256) = sysclk/(1*256) = 44.1kHz
+	 * f₂ = 4*2*sysclk = 90.3168Mhz
+	 *
+	 * PLL freq ration:
+	 *   R = f₂/mclk₁
+	 *   N = int(R) = 7
+	 *   K = 2²⁴*(R-N) = 3780644.9623
+	 *
+	 * dacdiv = /(1*256) → DAC at max rate
+	 *  → pick bclk rate 1.4112Mhz (sysclk/8)
+	 *  → bclkdiv = /8
+	 *
+	 * class D clk needs to be ~768kHz (700-800)
+	 *  → sysclk/768000 = 14
+	 *  → dclkdiv = /16 → dclk = 705.6kHz
+	 */
+	wr(0x1a, reg1a = reg1a & ~(1<<0)); /* disable pll */
+
+	wr(0x04,
+		0<<3 | /* dacdiv → sysclk/(1*256) = 44100 */
+		2<<1 | /* sysclkdiv → /2 */
+		1<<0 | /* clksel → pll output */
+		0
+	);
+	wr(0x34,
+		1<<5 | /* enable fractional mode */
+		1<<4 | /* pllprescale */
+		7<<0 | /* N */
+		0
+	);
+	k = s == 44100 ? 3780645 : 14500883; /* K */
+	wr(0x35, (k>>16) & 0xff);
+	wr(0x36, (k>>8) & 0xff);
+	wr(0x37, k & 0xff);
+
+	wr(0x08, 7<<6 | 7<<0); /* dclkdiv → sysclk/16; bclkdiv → sysclk/8 */
+	wr(0x1a, reg1a = reg1a | 1<<0); /* enable pll */
+
+	rate = s;
+
+	return 0;
+}
+
+static void
+reset(void)
+{
+	int i;
+
+	for(i = 0; i < Nout; i++){
+		out[i].vol[0] = -1;
+		out[i].vol[1] = -1;
+	}
+
+	toggle(out+Dac, 0);
+	wr(0x1c, 1<<7 | 1<<4 | 1<<3 | 1<<2); /* Vmid/r bias; Vgs/r on; Vmid soft start */
+	wr(0x19, 0<<7); /* Vmid off, Vref off */
+	sleep(500);
+	wr(0x0f, 0); /* reset registers to default */
+
+	setrate(rate);
+	set3d(⅓d);
+
+	wr(0x07, 1<<6 | 2); /* master mode; i²s, 16-bit words */
+
+	wr(0x17, 1<<8 | 3<<6 | 0<<1); /* thermal shutdown on; avdd=3.3v; slow clock on */
+
+	wr(0x06, 1<<3 | 1<<2); /* ramp up DAC volume slowly */
+	wr(0x2f, 3<<2); /* output mixer on */
+	wr(0x22, 1<<8); /* L DAC to mixer */
+	wr(0x25, 1<<8); /* R DAC to mixer */
+
+	wr(0x17, 1<<8 | 3<<6 | 1<<0); /* thermal shutdown on; avdd=3.3v; slow clock on */
+	wr(0x1c, 1<<7 | 1<<4 | 1<<3 | 1<<2); /* Vmid/r bias; Vgs/r on; Vmid soft start */
+	wr(0x19, 1<<7); /* start Vmid (playback) */
+	sleep(650);
+	wr(0x1c, 1<<3); /* done with anti-pop */
+	wr(0x19, 1<<7 | 1<<6); /* Vref on */
+
+	wr(0x09, 1<<6); /* adclrc → gpio (for jack detect output) */
+	wr(0x30, 3<<4 | 2<<2 | 1<<1); /* JD2 jack detect in; Tsense on */
+	wr(0x1b, 1<<3); /* HP_[LR] responsive to jack detect */
+	wr(0x18, 1<<6); /* HP switch on; high = HP */
+
+	/* turn on all outputs */
+	toggle(out+Hp, 1);
+	toggle(out+Spk, 1);
+	toggle(out+Dac, 1);
+
+	/* sensible defaults */
+	setvol(out+Spk, 100, 100);
+	setvol(out+Hp, 75, 75);
+	setvol(out+Dac, 80, 80);
+
+	/*
+	 * Jack detect becomes extremely unstable when playing on spk and the
+	 * volume is very high - DAC start to switch back and forth between two
+	 * outputs.  Solve this by always attenuating by -6dB and somewhat limiting
+	 * the volume on DAC ("master") - to 0xf9 instead of 0xff (max).
+	 */
+	wr(0x05, 1<<7 | 0<<3); /* unmute DAC */
+}
+
+static void
+fsread(Req *r)
+{
+	char msg[256], *s, *e;
+	Out *o;
+	int i;
+
+	s = msg;
+	e = msg+sizeof(msg);
+	*s = 0;
+	if(r->fid->file->aux == (void*)Ctl){
+		for(i = 0, o = out; i < Nout; i++, o++)
+			s = seprint(s, e, "%s %s\n", o->name, o->on ? "on" : "off");
+	}else if(r->fid->file->aux == (void*)Vol){
+		for(i = 0, o = out; i < Nout; i++, o++)
+			s = seprint(s, e, "%s %d %d\n", o->name, o->vol[0], o->vol[1]);
+		s = seprint(s, e, "speed %d\n", rate);
+		seprint(s, e, "3d %d\n", ⅓d);
+	}
+
+	readstr(r, msg);
+	respond(r, nil);
+}
+
+static int
+setoradd(int x, char *s)
+{
+	int d;
+
+	d = atoi(s);
+	if(*s == '+' || *s == '-')
+		return x + d;
+
+	return d;
+}
+
+static void
+fswrite(Req *r)
+{
+	int nf, on, i, vl, vr;
+	char msg[256], *f[4];
+	Out *o;
+
+	snprint(msg, sizeof(msg), "%.*s",
+		utfnlen((char*)r->ifcall.data, r->ifcall.count), (char*)r->ifcall.data);
+	nf = tokenize(msg, f, nelem(f));
+	if(nf == 1 && strcmp(f[0], "reset") == 0){
+		reset();
+		goto Done;
+	}else if(nf == 2 && strcmp(f[0], "speed") == 0){
+		if(setrate(atoi(f[1])) != 0){
+			respond(r, "not supported");
+			return;
+		}
+		goto Done;
+	}else if(nf == 2 && strcmp(f[0], "3d") == 0){
+		set3d(atoi(f[1]));
+		goto Done;
+	}
+	if(nf < 2){
+Emsg:
+		respond(r, "invalid ctl message");
+		return;
+	}
+
+	for(i = 0, o = out; i < Nout && strcmp(f[0], o->name) != 0; i++, o++)
+		;
+	if(i >= Nout)
+		goto Emsg;
+
+	if(r->fid->file->aux == (void*)Ctl){
+		if(nf != 2)
+			goto Emsg;
+		if(strcmp(f[1], "on") == 0)
+			on = 1;
+		else if(strcmp(f[1], "off") == 0)
+			on = 0;
+		else if(strcmp(f[1], "toggle") == 0)
+			on = !o->on;
+		else
+			goto Emsg;
+		toggle(o, on);
+	}else if(r->fid->file->aux == (void*)Vol){
+		vl = setoradd(o->vol[0], f[1]);
+		vr = setoradd(o->vol[1], nf < 3 ? f[1] : f[2]);
+		setvol(o, vl, vr);
+	}
+
+Done:
+	r->ofcall.count = r->ifcall.count;
+	respond(r, nil);
+}
+
+static Srv fs = {
+	.read = fsread,
+	.write = fswrite,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-1] [-D] [-m /dev] [-s service]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *mtpt, *srv;
+	int ctl, oneshot;
+
+	mtpt = "/dev";
+	srv = nil;
+	oneshot = 0;
+	ARGBEGIN{
+	case '1':
+		oneshot = 1;
+		break;
+	case 'D':
+		chatty9p = 1;
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if((data = open("#J/i2c3/i2c.1a.data", OWRITE)) < 0)
+		sysfatal("i2c data: %r");
+	if((ctl = open("#J/i2c3/i2c.1a.ctl", OWRITE)) < 0)
+		sysfatal("i2c ctl: %r");
+	fprint(ctl, "subaddress 1\n");
+	fprint(ctl, "size %d\n", 0x38<<1);
+	close(ctl);
+
+	reset();
+
+	if(oneshot)
+		exits(nil);
+
+	fs.tree = alloctree(uid, uid, DMDIR|0555, nil);
+	createfile(fs.tree->root, "audioctl", uid, 0666, (void*)Ctl);
+	createfile(fs.tree->root, "volume", uid, 0666, (void*)Vol);
+	/* have to mount -b to shadow sai's useless files */
+	postmountsrv(&fs, srv, mtpt, MBEFORE);
+
+	exits(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/reform/mkfile
@@ -1,0 +1,13 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/reform
+TARG=\
+	audio\
+	pm\
+
+</sys/src/cmd/mkmany
+
+install:V: $BIN
+
+$BIN:
+	mkdir -p $BIN
--- /dev/null
+++ b/sys/src/cmd/reform/pm.c
@@ -1,0 +1,243 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+
+enum
+{
+	Mhz = 1000*1000,
+	Pwmsrcclk = 25*Mhz,
+
+	Light = 1,
+	Temp,
+
+	PWMSAR = 0x0c/4,
+	PWMPR = 0x10/4,
+
+	TMUTMR = 0x00/4,
+		TMR_ME = 1<<31,
+		TMR_ALPF_SHIFT = 26,
+		TMR_MSITE_SHIFT = 13,
+	TMUTSR = 0x04/4,
+		TSR_MIE = 1<<30,
+		TSR_ORL = 1<<29,
+		TSR_ORH = 1<<28,
+	TMUTMTMIR = 0x08/4,
+	TMUTIER = 0x20/4,
+	TMUTIDR = 0x24/4,
+		TIDR_MASK = 0xe0000000,
+	TMUTISCR = 0x28/4,
+	TMUTICSCR = 0x2c/4,
+	TMUTTCFGR = 0x80/4,
+	TMUTSCFGR = 0x84/4,
+	TMUTRITSR0 = 0x100/4,
+	TMUTRITSR1 = 0x110/4,
+	TMUTRITSR2 = 0x120/4,
+	TMUTTR0CR = 0xf10/4,
+	TMUTTR1CR = 0xf14/4,
+	TMUTTR2CR = 0xf18/4,
+	TMUTTR3CR = 0xf1c/4,
+		CR_CAL_PTR_SHIFT = 16,
+	
+};
+
+static u32int *pwm2, *tmu;
+static char *uid = "pm";
+
+static void
+wr(u32int *base, int reg, u32int v)
+{
+	//fprint(2, "[0%x] ← 0x%ux\n", reg*4, v);
+	if(base != nil)
+		base[reg] = v;
+}
+
+static u32int
+rd(u32int *base, int reg)
+{
+	return base != nil ? base[reg] : -1;
+}
+
+static void
+setlight(int p)
+{
+	u32int v;
+
+	if(p < 0)
+		p = 0;
+	if(p > 100)
+		p = 100;
+
+	v = Pwmsrcclk / rd(pwm2, PWMSAR);
+	wr(pwm2, PWMPR, (Pwmsrcclk/(v*p/100))-2);
+}
+
+static int
+getlight(void)
+{
+	u32int m, v;
+
+	m = Pwmsrcclk / rd(pwm2, PWMSAR);
+	v = Pwmsrcclk / (rd(pwm2, PWMPR)+2);
+	return v*100/m;
+}
+
+static int
+getcputemp(int c[3])
+{
+	int i, r[] = {TMUTRITSR0, TMUTRITSR1, TMUTRITSR2};
+	u32int s;
+
+	s = rd(tmu, TMUTSR);
+	if(s & TSR_MIE){
+		werrstr("monitoring interval exceeded");
+		return -1;
+	}
+	if(s & (TSR_ORL|TSR_ORH)){
+		werrstr("out of range");
+		return -1;
+	}
+
+	c[0] = c[1] = c[2] = 0;
+	for(;;){
+		for(i = 0; i < 3; i++)
+			if(c[i] >= 0)
+				c[i] = rd(tmu, r[i]);
+		if(c[0] < 0 && c[1] < 0 && c[2] < 0)
+			break;
+		sleep(10);
+	}
+	c[0] &= 0xff;
+	c[1] &= 0xff;
+	c[2] &= 0xff;
+	return 0;
+}
+
+static void
+tmuinit(void)
+{
+	/* without proper calibration data sensing is useless */
+	static u8int cfg[4][12] = {
+		{0x23, 0x29, 0x2f, 0x35, 0x3d, 0x43, 0x4b, 0x51, 0x57, 0x5f, 0x67, 0x6f},
+		{0x1b, 0x23, 0x2b, 0x33, 0x3b, 0x43, 0x4b, 0x55, 0x5d, 0x67, 0x70, 0},
+		{0x17, 0x23, 0x2d, 0x37, 0x41, 0x4b, 0x57, 0x63, 0x6f, 0},
+		{0x15, 0x21, 0x2d, 0x39, 0x45, 0x53, 0x5f, 0x71, 0},
+	};
+	int i, j;
+
+	wr(tmu, TMUTMR, 0); /* disable */
+	wr(tmu, TMUTIER, 0); /* disable all interrupts */
+	wr(tmu, TMUTMTMIR, 0xf); /* no monitoring interval */
+
+	/* configure default ranges */
+	wr(tmu, TMUTTR0CR, 11<<CR_CAL_PTR_SHIFT | 0);
+	wr(tmu, TMUTTR1CR, 10<<CR_CAL_PTR_SHIFT | 38);
+	wr(tmu, TMUTTR2CR, 8<<CR_CAL_PTR_SHIFT | 72);
+	wr(tmu, TMUTTR3CR, 7<<CR_CAL_PTR_SHIFT | 97);
+
+	/* calibration data */
+	for(i = 0; i < 4; i++){
+		for(j = 0; j < 12 && cfg[i][j] != 0; j++){
+			wr(tmu, TMUTTCFGR, i<<16|j);
+			wr(tmu, TMUTSCFGR, cfg[i][j]);
+		}
+	}
+
+	/* enable: all sites, ALPF 11=0.125 */
+	wr(tmu, TMUTMR, TMR_ME | 3<<TMR_ALPF_SHIFT | 7<<TMR_MSITE_SHIFT);
+}
+
+static void
+fsread(Req *r)
+{
+	char msg[256];
+	int c[3];
+
+	msg[0] = 0;
+	if(r->ifcall.offset == 0){
+		if(r->fid->file->aux == (void*)Light)
+			snprint(msg, sizeof(msg), "lcd %d\n", getlight());
+		else if(r->fid->file->aux == (void*)Temp){
+			if(getcputemp(c) == 0)
+				snprint(msg, sizeof(msg), "%d.0\n", c[0]);
+			else
+				snprint(msg, sizeof(msg), "%r\n");
+		}
+	}
+
+	readstr(r, msg);
+	respond(r, nil);
+}
+
+static void
+fswrite(Req *r)
+{
+	char msg[256], *f[4];
+	int nf, v;
+
+	if(r->fid->file->aux == (void*)Light){
+		snprint(msg, sizeof(msg), "%.*s",
+			utfnlen((char*)r->ifcall.data, r->ifcall.count), (char*)r->ifcall.data);
+		nf = tokenize(msg, f, nelem(f));
+		if(nf < 2){
+			respond(r, "invalid ctl message");
+			return;
+		}
+		if(strcmp(f[0], "lcd") == 0){
+			v = atoi(f[1]);
+			if(*f[1] == '+' || *f[1] == '-')
+				v += getlight();
+			setlight(v);
+		}
+	}
+
+	r->ofcall.count = r->ifcall.count;
+	respond(r, nil);
+}
+
+static Srv fs = {
+	.read = fsread,
+	.write = fswrite,
+};
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-D] [-m /dev] [-s service]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *mtpt, *srv;
+
+	mtpt = "/dev";
+	srv = nil;
+	ARGBEGIN{
+	case 'D':
+		chatty9p = 1;
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if((tmu = segattach(0, "tmu", 0, 0xf20)) == (void*)-1)
+		sysfatal("no tmu");
+	if((pwm2 = segattach(0, "pwm2", 0, 0x18)) == (void*)-1)
+		sysfatal("no pwm2");
+	tmuinit();
+	fs.tree = alloctree(uid, uid, DMDIR|0555, nil);
+	createfile(fs.tree->root, "cputemp", uid, 0444, (void*)Temp);
+	createfile(fs.tree->root, "light", uid, 0666, (void*)Light);
+	postmountsrv(&fs, srv, mtpt, MAFTER);
+
+	exits(nil);
+}