ref: 9a01a24aa53d55dc1f3c4a7c4bee13fe60b6467a
parent: d86a7ed412555192e2000a9a34b3372f380ec3d0
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Wed Aug 17 09:10:21 EDT 2022
aux/wm8960: audio controls fs for MNT Reform
--- a/sys/src/cmd/aux/mkfile
+++ b/sys/src/cmd/aux/mkfile
@@ -50,6 +50,7 @@
write\
wacom\
wikifmt\
+ wm8960\
wpa\
zerotrunc\
--- /dev/null
+++ b/sys/src/cmd/aux/wm8960.c
@@ -1,0 +1,255 @@
+#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,
+ Spk,
+ Hp,
+ Nout,
+
+ Ctl = 1,
+ Vol,
+};
+
+struct Out
+{
+ char *name;
+ int volreg;
+ int volmax;
+ void (*toggle)(Out *o, int on);
+ int on;
+ int vol[2];
+};
+
+static char *uid = "audio";
+static int data, reg1a;
+
+static void
+wr(int a, int v)
+{
+ u8int c;
+
+ //fprint(2, "[0x%x] ← 0x%ux = ", a, v & 0x1ff);
+ c = v & 0xff;
+ pwrite(data, &c, 1, a<<1 | ((v>>8)&1));
+}
+
+static void
+dactoggle(Out *o, int on)
+{
+ if(o->on = on)
+ reg1a |= 3<<7;
+ else
+ reg1a &= ~(3<<7);
+ wr(0x1a, reg1a);
+}
+
+static void
+spktoggle(Out *o, int on)
+{
+ if(o->on = on)
+ reg1a |= 3<<3;
+ else
+ reg1a &= ~(3<<3);
+ wr(0x31, (on ? 3 : 0)<<6); /* class D SPK out */
+ wr(0x1a, reg1a);
+}
+
+static void
+hptoggle(Out *o, int on)
+{
+ if(o->on = on)
+ reg1a |= 3<<5;
+ else
+ reg1a &= ~(3<<5);
+ wr(0x1a, reg1a);
+}
+
+static Out out[Nout] =
+{
+ [Dac] = {"master", 0x0a, 0xff, dactoggle, 0},
+ [Spk] = {"spk", 0x28, 0x7f, spktoggle, 0},
+ [Hp] = {"hp", 0x02, 0x7f, hptoggle, 0},
+};
+
+static void
+setvol(Out *o, int l, int r)
+{
+ int zc;
+
+ l = CLAMP(l, 0, 100);
+ r = CLAMP(r, 0, 100);
+
+ o->vol[0] = l;
+ o->vol[1] = r;
+
+ 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, 0<<8 | zc<<7 | r);
+ wr(o->volreg+1, 1<<8 | zc<<7 | r);
+}
+
+static void
+reset(void)
+{
+ Out *o;
+ int i;
+
+ wr(0x0f, 0); /* reset registers to default */
+ wr(0x04, 0); /* sysclk (div 1) derived from mclk; dacdiv=sysclk/256 */
+ wr(0x05, 0<<3); /* unmute DAC */
+ wr(0x06, 1<<3 | 1<<2); /* ramp up DAC volume slowly */
+ wr(0x07, 2); /* i²s, 16-bit words, slave mode */
+ wr(0x08, 7<<6); /* class D divider: sysclk/16 */
+ wr(0x19, 1<<7 | 1<<6); /* Vmid = playback, VREF on */
+ wr(0x22, 1<<8); /* L DAC to mixer */
+ wr(0x25, 1<<8); /* R DAC to mixer */
+ wr(0x2f, 3<<2); /* output mixer on */
+ wr(0x30, 1<<1); /* Tsense on */
+ wr(0x33, 5<<0); /* +5.1dB AC SPK boost - Reform's speakers can be too quiet */
+
+ /* sensible defaults */
+ setvol(&out[Dac], 100, 100);
+ setvol(&out[Spk], 80, 80);
+ setvol(&out[Hp], 65, 65);
+
+ /* enable every output and let the user decide later */
+ for(i = 0, o = out; i < Nout; i++, o++)
+ o->toggle(o, 1);
+}
+
+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]);
+ }
+
+ readstr(r, msg);
+ respond(r, nil);
+}
+
+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);
+ if((nf = tokenize(msg, f, nelem(f))) < 2){
+ if(nf == 1 && strcmp(f[0], "reset") == 0){
+ reset();
+ goto Done;
+ }
+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;
+ o->toggle(o, on);
+ }else if(r->fid->file->aux == (void*)Vol){
+ vl = atoi(f[1]);
+ vr = nf < 3 ? vl : atoi(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\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *mtpt, *srv;
+ int ctl;
+
+ mtpt = "/mnt/wm8960";
+ srv = nil;
+ ARGBEGIN{
+ 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();
+
+ 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);
+}