shithub: riscv

ref: c8e25d2a18c0395431abc5818a1d2f0561b0181f
dir: /sys/src/cmd/aux/wm8960.c/

View raw version
#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;

	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, 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]);
		seprint(s, e, "speed 44100\n");
	}

	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);
}