shithub: riscv

Download patch

ref: 9a6bf7f0787d050429da8b22a82c89ab5d869943
parent: 91f79ce9fab7c9920b6f331ca074d58c17755fc9
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Wed Aug 17 19:24:42 EDT 2022

imx8: add a semi-working SAI2 audio driver

--- a/sys/src/9/imx8/io.h
+++ b/sys/src/9/imx8/io.h
@@ -30,6 +30,8 @@
 
 	IRQpci2		= SPI+74,
 
+	IRQsai2		= SPI+96,
+
 	IRQenet1	= SPI+118,
 
 	IRQpci1		= SPI+122,
--- a/sys/src/9/imx8/reform
+++ b/sys/src/9/imx8/reform
@@ -22,6 +22,7 @@
 	i2c
 	rtc	devi2c
 	sd
+	audio
 
 link
 	usbxhciimx
@@ -31,6 +32,7 @@
 	netdevmedium
 	i2cimx		devi2c
 	pciimx		pci
+	sai
 
 ip
 	tcp
--- /dev/null
+++ b/sys/src/9/imx8/sai.c
@@ -1,0 +1,393 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/audioif.h"
+#include "../port/error.h"
+
+typedef struct Ctlr Ctlr;
+typedef struct Ring Ring;
+
+enum {
+	Byteps = 4,
+
+	TCSR = 0x08/4,
+		TCSR_TE = 1<<31,
+		TCSR_FR = 1<<25,
+		TCSR_SR = 1<<24,
+		TCSR_FEF = 1<<18, /* w1c fifo error */
+		TCSR_FWF = 1<<17,
+		TCSR_FRF = 1<<16, /* watermark hit */
+		TCSR_FEIE = 1<<10,
+		TCSR_FWIE = 1<<9,
+		TCSR_FRIE = 1<<8,
+	TCR1 = 0x0c/4,
+	TCR2 = 0x10/4,
+		TCR2_MSEL_MCLK1 = 1<<26,
+		TCR2_BCP = 1<<25,
+		TCR2_BCD_MASTER = 1<<24,
+	TCR3 = 0x14/4,
+		TCR3_TCE_SHIFT = 16,
+	TCR4 = 0x18/4,
+		TCR4_FPACK_16BIT = 3<<24,
+		TCR4_FRSZ_SHIFT = 16,
+		TCR4_SYWD_SHIFT = 8,
+		TCR4_CHMOD = 1<<5,
+		TCR4_MSB_FIRST = 1<<4,
+		TCR4_FSE = 1<<3,
+		TCR4_FSP_LOW = 1<<1,
+		TCR4_FSD_MASTER = 1<<0,
+	TCR5 = 0x1c/4,
+		TCR5_WNW_SHIFT = 24,
+		TCR5_W0W_SHIFT = 16,
+		TCR5_FBT_SHIFT = 8,
+	TDR0 = 0x20/4,
+	TFR0 = 0x40/4,
+		TFRx_WFP_SHIFT = 16,
+		TFRx_RFP_SHIFT = 0,
+	TMR = 0x60/4,
+};
+
+struct Ring {
+	Rendez r;
+
+	uchar *buf;
+	ulong nbuf;
+
+	ulong ri;
+	ulong wi;
+};
+
+struct Ctlr {
+	u32int *reg;
+	Audio *adev;
+
+	Ring w;
+	int wactive;
+
+	Lock;
+};
+
+#define wr(a, v) ctlr->reg[a] = v
+#define rd(a) ctlr->reg[a]
+
+static long
+buffered(Ring *r)
+{
+	ulong ri, wi;
+
+	ri = r->ri;
+	wi = r->wi;
+	if(wi >= ri)
+		return wi - ri;
+	else
+		return r->nbuf - (ri - wi);
+}
+
+static long
+available(Ring *r)
+{
+	long m;
+
+	m = (r->nbuf - Byteps) - buffered(r);
+	if(m < 0)
+		m = 0;
+	return m;
+}
+
+static long
+writering(Ring *r, uchar *p, long n)
+{
+	long n0, m;
+
+	n0 = n;
+	while(n > 0){
+		if((m = available(r)) <= 0)
+			break;
+		if(m > n)
+			m = n;
+		if(p){
+			if(r->wi + m > r->nbuf)
+				m = r->nbuf - r->wi;
+			memmove(r->buf + r->wi, p, m);
+			p += m;
+		}
+		r->wi = (r->wi + m) % r->nbuf;
+		n -= m;
+	}
+	return n0 - n;
+}
+
+static int
+outavail(void *arg)
+{
+	Ring *r = arg;
+
+	return available(r) > 0;
+}
+
+static int
+outrate(void *arg)
+{
+	Ctlr *ctlr = arg;
+	int delay = ctlr->adev->delay*Byteps;
+
+	return delay <= 0 || buffered(&ctlr->w) <= delay || ctlr->wactive == 0;
+}
+
+static void
+saikick(Ctlr *ctlr)
+{
+	int delay;
+
+	delay = ctlr->adev->delay*Byteps;
+	if(buffered(&ctlr->w) >= delay){
+		ctlr->wactive = 1;
+		/* activate channel 1 */
+		wr(TCR3, 1<<TCR3_TCE_SHIFT);
+		wr(TCSR, TCSR_TE | TCSR_FEF | TCSR_FRIE | TCSR_FWIE | TCSR_FEIE);
+	}
+}
+
+static void
+saistop(Ctlr *ctlr)
+{
+	if(!ctlr->wactive)
+		return;
+	ctlr->wactive = 0;
+	wr(TCSR, TCSR_FR | TCSR_SR);
+}
+
+static long
+saiwrite(Audio *adev, void *a, long n, vlong)
+{
+	Ctlr *ctlr = adev->ctlr;
+	uchar *p, *e;
+	Ring *r;
+
+	p = a;
+	e = p + n;
+	r = &ctlr->w;
+	while(p < e){
+		if((n = writering(r, p, e - p)) <= 0){
+			saikick(ctlr);
+			sleep(&r->r, outavail, r);
+			continue;
+		}
+		p += n;
+	}
+	saikick(ctlr);
+	while(outrate(ctlr) == 0)
+		sleep(&r->r, outrate, ctlr);
+	return p - (uchar*)a;
+}
+
+static void
+saiclose(Audio *adev, int mode)
+{
+	Ctlr *ctlr = adev->ctlr;
+
+	if(mode == OWRITE || mode == ORDWR)
+		saistop(ctlr);
+}
+
+static void
+saireset(Ctlr *ctlr)
+{
+	/* fifo+software reset */
+	wr(TCSR, TCSR_FR | TCSR_SR);
+	delay(1);
+	wr(TCSR, 0);
+	delay(1);
+
+	/* watermark - hit early enough */
+	wr(TCR1, 32);
+	/* derive from mclk1; generate bitclock (active low), f = mclk1/(8+1)*2 = mclk1/18 */
+	wr(TCR2, TCR2_MSEL_MCLK1 | TCR2_BCD_MASTER | TCR2_BCP | 8);
+	/* activate channel 1 */
+	wr(TCR3, 1<<TCR3_TCE_SHIFT);
+	/* set up for i²s */
+	wr(TCR4,
+		TCR4_CHMOD | /* output mode, no TDM */
+		TCR4_MSB_FIRST |
+		TCR4_FPACK_16BIT | /* 16-bit packed words */
+		1<<TCR4_FRSZ_SHIFT | /* two words per frame */
+		15<<TCR4_SYWD_SHIFT | /* frame sync per word */
+		/* frame sync */
+		TCR4_FSE | /* one bit earlier */
+		TCR4_FSP_LOW | /* active high */
+		TCR4_FSD_MASTER /* generate internally */
+	);
+	/* 16-bit words, MSB first */
+	wr(TCR5, 15<<TCR5_WNW_SHIFT | 15<<TCR5_W0W_SHIFT | 15<<TCR5_FBT_SHIFT);
+	/* mask all but first two words */
+	wr(TMR, ~3UL);
+}
+
+static long
+saictl(Audio *adev, void *a, long n, vlong)
+{
+	char *p, *e, *x, *tok[4];
+	Ctlr *ctlr = adev->ctlr;
+	int ntok;
+	u32int v;
+
+	p = a;
+	e = p + n;
+	for(; p < e; p = x){
+		if(x = strchr(p, '\n'))
+			*x++ = 0;
+		else
+			x = e;
+		ntok = tokenize(p, tok, 4);
+		if(ntok <= 0)
+			continue;
+
+		if(cistrcmp(tok[0], "div") == 0 && ntok >= 2){
+			v = strtoul(tok[1], nil, 0);
+			wr(TCR2, (rd(TCR2) & ~0xff) | (v & 0xff));
+		}else if(cistrcmp(tok[0], "msel") == 0 && ntok >= 2){
+			v = strtoul(tok[1], nil, 0);
+			wr(TCR2, (rd(TCR2) & ~(3<<26)) | (v & 3)<<26);
+		}else if(cistrcmp(tok[0], "reset") == 0){
+			saireset(ctlr);
+		}else
+			error(Ebadctl);
+	}
+	return n;
+}
+
+static long
+fifo(Ctlr *ctlr, long n)
+{
+	long n0, m;
+	u32int *p;
+	Ring *r;
+
+	n0 = n;
+	r = &ctlr->w;
+	while(n > 0){
+		if((m = buffered(r)) <= 0 || m < 4)
+			break;
+		if(m > n)
+			m = n;
+
+		if(r->ri + m > r->nbuf)
+			m = r->nbuf - r->ri;
+		m &= ~(Byteps-1);
+		for(p = (u32int*)(r->buf + r->ri); p < (u32int*)(r->buf + r->ri + m); p++)
+			wr(TDR0, *p);
+
+		r->ri = (r->ri + m) % r->nbuf;
+		n -= m;
+	}
+	return n0 - n;
+}
+
+static void
+saiinterrupt(Ureg *, void *arg)
+{
+	Ctlr *ctlr = arg;
+	u32int v;
+	Ring *r;
+
+	ilock(ctlr);
+	v = rd(TCSR);
+	if(v & (TCSR_FEF | TCSR_FRF | TCSR_FWF)){
+		r = &ctlr->w;
+		if(ctlr->wactive){
+			if(buffered(r) < 128*Byteps) /* having less than fifo buffered */
+				saistop(ctlr);
+			else if(fifo(ctlr, (128-32)*Byteps) > 0)
+				v |= TCSR_TE;
+		}
+		wakeup(&r->r);
+	}
+	wr(TCSR, v | TCSR_FEF);
+	iunlock(ctlr);
+}
+
+static long
+saistatus(Audio *adev, void *a, long n, vlong)
+{
+	Ctlr *ctlr = adev->ctlr;
+	u32int v, p;
+	char *s, *e;
+
+	s = a;
+	e = s + n;
+	v = rd(TCSR);
+	p = rd(TFR0);
+	s = seprint(s, e, "transmit wfp %d rfp %d delay %d buf %ld avail %ld active %d %s%s%s%s\n",
+		(p>>TFRx_WFP_SHIFT) & 0xff,
+		(p>>TFRx_RFP_SHIFT) & 0xff,
+		adev->delay,
+		buffered(&ctlr->w),
+		available(&ctlr->w),
+		ctlr->wactive,
+		(v & TCSR_TE) ? " enabled" : "",
+		(v & TCSR_FEF) ? " fifo_error" : "",
+		(v & TCSR_FWF) ? " fifo_warn" : "",
+		(v & TCSR_FRF) ? " fifo_req" : ""
+	);
+
+	return s - (char*)a;
+}
+
+static long
+saibuffered(Audio *adev)
+{
+	Ctlr *ctlr = adev->ctlr;
+
+	return buffered(&ctlr->w);
+}
+
+static int
+saiprobe(Audio *adev)
+{
+	Ctlr *ctlr;
+
+	if(adev->ctlrno > 0)
+		return -1;
+
+	ctlr = mallocz(sizeof(Ctlr), 1);
+	if(ctlr == nil)
+		return -1;
+
+	ctlr->w.buf = malloc(ctlr->w.nbuf = 44100*Byteps*2);
+	ctlr->reg = (u32int*)(VIRTIO + 0x8b0000);
+	ctlr->adev = adev;
+
+	adev->delay = 1024;
+	adev->ctlr = ctlr;
+	adev->write = saiwrite;
+	adev->close = saiclose;
+	adev->buffered = saibuffered;
+	adev->status = saistatus;
+	adev->ctl = saictl;
+
+	saireset(ctlr);
+
+	intrenable(IRQsai2, saiinterrupt, ctlr, BUSUNKNOWN, "sai2");
+
+	return 0;
+}
+
+void
+sailink(void)
+{
+
+	iomuxpad("pad_sai2_rxfs", "sai2_rx_sync", "SION ~LVTTL HYS PUE ~ODE FAST 45_OHM VSEL_0");
+	iomuxpad("pad_sai2_rxc", "sai2_rx_bclk", "SION ~LVTTL HYS PUE ~ODE FAST 45_OHM VSEL_0");
+	iomuxpad("pad_sai2_rxd0", "sai2_rx_data0", "SION ~LVTTL HYS PUE ~ODE FAST 45_OHM VSEL_0");
+	iomuxpad("pad_sai2_txfs", "sai2_tx_sync", "SION ~LVTTL HYS PUE ~ODE FAST 45_OHM VSEL_0");
+	iomuxpad("pad_sai2_txc", "sai2_tx_bclk", "SION ~LVTTL HYS PUE ~ODE FAST 45_OHM VSEL_0");
+	iomuxpad("pad_sai2_txd0", "sai2_tx_data0", "SION ~LVTTL HYS PUE ~ODE FAST 45_OHM VSEL_0");
+	iomuxpad("pad_sai2_mclk", "sai2_mclk", "SION ~LVTTL HYS PUE ~ODE FAST 45_OHM VSEL_0");
+
+	setclkgate("sai2.ipg_clk", 1);
+
+	addaudiocard("sai", saiprobe);
+}