diff options
author | Sigrid Solveig Haflínudóttir <sigrid@ftrv.se> | 2022-08-21 01:30:03 +0000 |
---|---|---|
committer | Sigrid Solveig Haflínudóttir <sigrid@ftrv.se> | 2022-08-21 01:30:03 +0000 |
commit | a78b71b143240fa1dad019f5c655a03c2d2400d9 (patch) | |
tree | e89536e837fbd4a921542d81e73d7315b4da3ac4 /sys/src/cmd/reform/audio.c | |
parent | 5e15db8fa31dd68fee22f260ae797a38ccaa4070 (diff) |
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
Diffstat (limited to 'sys/src/cmd/reform/audio.c')
-rw-r--r-- | sys/src/cmd/reform/audio.c | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/sys/src/cmd/reform/audio.c b/sys/src/cmd/reform/audio.c new file mode 100644 index 000000000..9c73505b6 --- /dev/null +++ b/sys/src/cmd/reform/audio.c @@ -0,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); +} |