ref: 2f850b4e65c8750b40a4447d11f4b0e0217997a2
dir: /sys/src/cmd/reform/audio.c/
#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
dactoggle(Out *, int on)
{
wr(0x05, (!on)<<3);
}
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, 0xff, 0, dactoggle, 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:
*
* f₁ = 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:
* R = f₂/f₁
* 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); /* power down */
sleep(200);
wr(0x0f, 0); /* reset registers to default */
setrate(rate);
set3d(⅓d);
wr(0x07, 1<<6 | 0<<2 | 2<<0); /* master mode; 16 bits; i²s */
wr(0x06, 1<<3 | 0<<2); /* soft mute; ramp up DAC volume fast */
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<<1 | 1<<0); /* thermal shutdown on; avdd=3.3v; faster response; 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); /* gpio jack detect out; JD2 jack detect in; Tsense on */
wr(0x1b, 1<<6 | 0<<3 | 0<<0); /* 20kΩ; capless mode disabled; 44.1/48kHz */
wr(0x18, 1<<6 | 0<<5); /* HP switch on; high = HP */
/* turn on all outputs */
toggle(out+Hp, 1);
toggle(out+Spk, 1);
/* enable/unmute DAC */
reg1a |= 3<<7;
wr(0x1a, reg1a);
toggle(out+Dac, 1);
/* sensible defaults */
setvol(out+Spk, 100, 100);
setvol(out+Hp, 75, 75);
setvol(out+Dac, 80, 80);
}
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);
}