Plan 9 from Bell Labs’s /usr/web/sources/contrib/tristan/kw/devaudio.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/* kw audio driver */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "io.h"

enum {
	Bufc = 4,
	Bufsz = 16 * 1024,
	Bufsize = Bufc * Bufsz,
};

typedef struct Audiokw Audiokw;
typedef struct Kwaudio Kwaudio;

struct Audiokw {
	Rendez vous;
	Kwaudio *reg;
	struct {
		QLock;
		long fill, off;
		ulong intr, flow;
		char *bp;
	} play, rec;
};

struct Kwaudio {
	char _unkn0[0x0a00];

	struct {	/* 0a00 */
#define WINATTR(v)      (((v) & MASK(8)) << 8)
#define WINSIZE(v)      (((v)/(64*1024) - 1) << 16)
		ulong base;
		ulong size;
	} recwin,playwin;
	char _unkn1[0x1000-0x0a10];

	struct {	/* 1000 and 1100 */
		ulong control;
		ulong buf, bufsz, bufoff;
		long pad[60];
	} recc, playc;

	struct {	/* 1200 */
		long _unkn0;
		ulong DCO;
		long _unkn2;
		ulong SPCR;
		long _unkn3[4];
		ulong sample_control, play_sample, record_sample;
		long _unkn4;
		ulong clocks;
		long _unkn5[3];
		long pad[48];
	} clock;

	struct {	/* 1300 */
		ulong err_cause, err_mask;
		ulong int_cause, int_mask;
		ulong rec_count, play_count;
		ulong play_cc, rec_cc;
		long pad[56];
	} interr;

	uchar _unkn2[0xc00];

	struct {	/* 2000 and 2200 */
		long unkn0;
		uint control;
		long unkn1[30];
		uint csl[6], csr[6], ubl[6], ubr[6];
		long pad[72];
	} spdif[2];

	struct {	/* 2400 and 2500 */
		long unkn0;
		ulong control;
		long pad[62];
	} reci2s, playi2s;
};

enum {
	int_rec = 1<<13,
	int_play = 1<<14,

	SZ_16c = 3<<0,	/* normal 16bit little endian i/o */
	CPause = 1<<9,
	Rec_DMA_128 = 2<<5,
	Rec_I2Sen = 1<<10,
	Play_I2Sen = 1<<3,
	Play_DMA_128 = 2<<11,

	I2S_ss_24 = 1<<30,	/* to codec */
	I2S_just_left = 0<<26,
};

static Audiokw audio = {
	.reg (Kwaudio *)Addraudio,
};

static int
recfill(void *)
{
	return audio.rec.fill;
}

static uint
recbuf(char *ib, uint sz)
{
	char *ie;
	uint len;
	ie = ib + sz;
	qlock(&audio.play);
	audio.reg->recc.control |= Rec_I2Sen;
	if(audio.rec.fill==Bufc/2) audio.reg->recc.control &= ~CPause;
	while(ib < ie){
		if(audio.rec.fill==0) sleep(&audio.vous, recfill, 0);
		len = MIN(-audio.rec.off % Bufsz + Bufsz, ie-ib);
		memmove(ib, audio.rec.bp + audio.rec.off, len);
		audio.rec.off = (audio.rec.off + len) % Bufsize;
		ib += len;
		if(audio.rec.off%Bufsz==0)
			audio.rec.fill--;
		if(audio.rec.fill==Bufc/2) audio.reg->recc.control &= ~CPause;
	}
	qunlock(&audio.rec);
	return sz;
}

static void
recopen(void)
{
	Kwaudio *reg = audio.reg;

	if(audio.rec.bp==nil)
		audio.rec.bp = mallocalign(Bufsize, 128, 0, 0);

	reg->recc.control = Rec_DMA_128 | CPause | SZ_16c;
	reg->reci2s.control = I2S_ss_24 | I2S_just_left;
	reg->recc.buf = PADDR(audio.rec.bp);
	reg->recc.bufsz = Bufsize/4-1;
	reg->recc.bufoff = audio.rec.off = 0;
	reg->interr.int_mask |= int_rec;
	reg->interr.rec_count = Bufsz;
	audio.rec.fill = 0;
}

static void
recint(void)
{
	audio.rec.fill++;
	audio.rec.intr++;
	if(audio.rec.fill>Bufc){
		audio.reg->recc.control |= CPause;
		audio.rec.flow++;
	}
}

static void
recclose(void)
{
	Kwaudio *reg = audio.reg;
	reg->recc.control |= CPause;
	reg->recc.control &= ~Rec_I2Sen;
	if(audio.rec.bp){
		free(audio.rec.bp);
		audio.rec.bp = nil;
		reg->recc.buf = 0;
		reg->recc.bufsz = 0;
	}
	if(audio.rec.locked)
		qunlock(&audio.rec);
}

static int
playfill(void *)
{
	return audio.play.fill;
}

static uint
playbuf(char *ib, uint sz)
{
	char *ie;
	uint len;
	ie = ib + sz;
	qlock(&audio.play);
	audio.reg->playc.control |= Play_I2Sen;
	while(ib < ie){
		if(audio.play.fill==0) sleep(&audio.vous, playfill, 0);
		len = MIN(-audio.play.off % Bufsz + Bufsz, ie-ib);
		memmove(audio.play.bp + audio.play.off, ib, len);
		audio.play.off = (audio.play.off + len) % Bufsize;
		ib += len;
		if(audio.play.off%Bufsz==0)
			audio.play.fill--;
		if(audio.play.fill==Bufc/2) audio.reg->playc.control &= ~CPause;
	}
	qunlock(&audio.play);
	return sz;
}

static void
playopen(void)
{
	Kwaudio *reg = audio.reg;

	if(audio.play.bp==nil)
		audio.play.bp = mallocalign(Bufsize, 128, 0, 0);

	reg->playc.control = Play_DMA_128 | CPause | SZ_16c;
	reg->playi2s.control = I2S_ss_24 | I2S_just_left;
	reg->playc.buf = PADDR(audio.play.bp);
	reg->playc.bufsz = Bufsize/4-1;
	reg->playc.bufoff = audio.play.off = 0;
	reg->interr.int_mask |= int_play;
	reg->interr.play_count = Bufsz;
	audio.play.fill = Bufc;
}

static void
playint(void)
{
	audio.play.fill++;
	audio.play.intr++;
	if(audio.play.fill>Bufc){
		audio.reg->playc.control |= CPause;
		audio.play.flow++;
	}
}

static void
playclose(void)
{
	Kwaudio *reg = audio.reg;
	reg->playc.control |= CPause;
	reg->playc.control &= ~Play_I2Sen;
	if(audio.play.bp){
		free(audio.play.bp);
		audio.play.bp = nil;
		reg->playc.buf = 0;
		reg->playc.bufsz = 0;
	}
	if(audio.play.locked)
		qunlock(&audio.play);
}

static void
interrupt(Ureg *, void *)
{
	Kwaudio *reg = audio.reg;

	if(reg->interr.int_cause & int_play)
		playint();
	if(reg->interr.int_cause & int_rec)
		recint();
	wakeup(&audio.vous);

	reg->interr.int_cause = reg->interr.int_cause;
	intrclear(Irqlo, IRQ0audio);
}

static void
audioinit(void)
{
	Kwaudio *reg = audio.reg;

	reg->recwin.base = reg->playwin.base = PHYSDRAM;
	reg->recwin.size = reg->playwin.size = WINSIZE(256*MB) | WINATTR(Attrcs0) | Targdram<<4 | 1;
	reg->interr.int_cause = ~0;
	reg->interr.int_mask = 0;

	intrenable(Irqlo, IRQ0audio, interrupt, &audio, "audiokw");
}

static void
audioshutdown(void)
{
	recclose();
	playclose();
	intrdisable(Irqlo, IRQ0audio, interrupt, &audio, "audiokw");
}

enum {
	Qdir,
	Qin,
	Qout,
	Qstat,
};

Dirtab
audiodir[] =
{
	".",	{Qdir, 0, QTDIR},		0,	DMDIR|0555,
	"audioin",	{Qin},		0,	0444,
	"audio",	{Qout},		0,	0222,
	"audiostat", {Qstat},		0,	0444,
};

static Chan*
audioattach(char *param)
{
	return devattach('A', param);
}

static Walkqid*
audiowalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
}

static int
audiostat(Chan *c, uchar *db, int n)
{
	audiodir[Qout].length = 0;
	return devstat(c, db, n, audiodir, nelem(audiodir), devgen);
}

static Chan*
audioopen(Chan *c, int omode)
{
	switch((ulong)c->qid.path){
	default:
		error(Eperm);
		break;
	case Qin:
		recopen();
		break;
	case Qout:
		playopen();
		break;
	case Qdir:
	case Qstat:
		break;
	}
	c = devopen(c, omode, audiodir, nelem(audiodir), devgen);
	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;

	return c;
}

static void
audioclose(Chan *c)
{
	switch((ulong)c->qid.path){
	default:
		error(Eperm);
		break;
	case Qin:
		recclose();
	case Qout:
		playclose();
		break;
	case Qdir:
	case Qstat:
		break;
	}
}

static long
audioread(Chan *c, void *v, long n, vlong off)
{
	char buf[256];
	switch((ulong)c->qid.path){
	default:
		error(Eperm);
		break;
	case Qdir:
		return devdirread(c, v, n, audiodir, nelem(audiodir), devgen);
		break;
	case Qin:
		return recbuf(v, n);
		break;
	case Qstat:
		snprint(buf, sizeof(buf),
			"	fill	off	flow	int\n"
			"rec	%ld	%ld	%uld	%uld\n"
			"play	%ld	%ld	%uld	%uld\n",
			audio.rec.fill, audio.rec.off, audio.rec.flow, audio.rec.intr,
			audio.play.fill, audio.play.off, audio.play.flow, audio.play.intr);
		return readstr(off, v, n, buf);
	}
	return 0;
}

static long
audiowrite(Chan *c, void *v, long n, vlong)
{
	switch((ulong)c->qid.path){
	default:
		error(Eperm);
		break;
	case Qout:
		return playbuf(v, n);
		break;
	}
	return 0;
}

Dev audiokwdevtab = {
	'A',
	"audio",

	devreset,
	audioinit,
	audioshutdown,
	audioattach,
	audiowalk,
	audiostat,
	audioopen,
	devcreate,
	audioclose,
	audioread,
	devbread,
	audiowrite,
	devbwrite,
	devremove,
	devwstat,
};

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].