#include <sys/param.h>
#include <sys/systm.h>
#include <sys/audioio.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/fcntl.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/ofw_power.h>
#include <dev/ofw/fdt.h>
#include <dev/audio_if.h>
#include <arm64/dev/apldma.h>
#define MCA_CL_STRIDE 0x4000
#define MCA_SW_STRIDE 0x8000
#define MCA_SERDES_TXA 0x0300
#define MCA_STATUS(idx) ((idx) * MCA_CL_STRIDE + 0x0000)
#define MCA_STATUS_MCLK_EN (1 << 0)
#define MCA_MCLK_CONF(idx) ((idx) * MCA_CL_STRIDE + 0x0004)
#define MCA_MCLK_CONF_DIV_MASK (0xf << 8)
#define MCA_MCLK_CONF_DIV_SHIFT 8
#define MCA_SYNCGEN_STATUS(idx) ((idx) * MCA_CL_STRIDE + 0x0100)
#define MCA_SYNCGEN_STATUS_EN (1 << 0)
#define MCA_SYNCGEN_MCLK_SEL(idx) ((idx) * MCA_CL_STRIDE + 0x0104)
#define MCA_SYNCGEN_HI_PERIOD(idx) ((idx) * MCA_CL_STRIDE + 0x0108)
#define MCA_SYNCGEN_LO_PERIOD(idx) ((idx) * MCA_CL_STRIDE + 0x010c)
#define MCA_SERDES_BASE(idx, off) ((idx) * MCA_CL_STRIDE + (off))
#define MCA_SERDES_STATUS(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0000)
#define MCA_SERDES_STATUS_EN (1 << 0)
#define MCA_SERDES_STATUS_RST (1 << 1)
#define MCA_SERDES_CONF(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0004)
#define MCA_SERDES_CONF_NSLOTS_MASK (0xf << 0)
#define MCA_SERDES_CONF_NSLOTS_SHIFT 0
#define MCA_SERDES_CONF_WIDTH_MASK (0x1f << 4)
#define MCA_SERDES_CONF_WIDTH_32BIT (0x10 << 4)
#define MCA_SERDES_CONF_BCLK_POL (1 << 10)
#define MCA_SERDES_CONF_MAGIC (0x7 << 12)
#define MCA_SERDES_CONF_SYNC_SEL_MASK (0x7 << 16)
#define MCA_SERDES_CONF_SYNC_SEL_SHIFT 16
#define MCA_SERDES_BITSTART(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0008)
#define MCA_SERDES_CHANMASK0(idx, off) (MCA_SERDES_BASE(idx, off) + 0x000c)
#define MCA_SERDES_CHANMASK1(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0010)
#define MCA_SERDES_CHANMASK2(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0014)
#define MCA_SERDES_CHANMASK3(idx, off) (MCA_SERDES_BASE(idx, off) + 0x0018)
#define MCA_PORT_ENABLE(idx) ((idx) * MCA_CL_STRIDE + 0x0600)
#define MCA_PORT_ENABLE_CLOCKS (0x3 << 1)
#define MCA_PORT_ENABLE_TX_DATA (1 << 3)
#define MCA_PORT_CLOCK_SEL(idx) ((idx) * MCA_CL_STRIDE + 0x0604)
#define MCA_PORT_CLOCK_SEL_SHIFT 8
#define MCA_PORT_DATA_SEL(idx) ((idx) * MCA_CL_STRIDE + 0x0608)
#define MCA_PORT_DATA_SEL_TXA(idx) (1 << ((idx) * 2))
#define MCA_PORT_DATA_SEL_TXB(idx) (2 << ((idx) * 2))
#define MCA_DMA_ADAPTER_A(idx) ((idx) * MCA_SW_STRIDE + 0x0000)
#define MCA_DMA_ADAPTER_B(idx) ((idx) * MCA_SW_STRIDE + 0x4000)
#define MCA_DMA_ADAPTER_TX_LSB_PAD_SHIFT 0
#define MCA_DMA_ADAPTER_TX_NCHANS_SHIFT 5
#define MCA_DMA_ADAPTER_NCHANS_SHIFT 20
#define HREAD4(sc, reg) \
(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
#define HWRITE4(sc, reg, val) \
bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
#define HSET4(sc, reg, bits) \
HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
#define HCLR4(sc, reg, bits) \
HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
struct aplmca_dai {
struct aplmca_softc *ad_sc;
struct dai_device ad_dai;
int ad_cluster;
struct apldma_channel *ad_ac;
void *ad_pbuf;
};
struct aplmca_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_space_handle_t sc_sw_ioh;
int sc_node;
uint32_t sc_phandle;
int sc_nclusters;
struct aplmca_dai *sc_ad;
};
int aplmca_set_format(void *, uint32_t, uint32_t, uint32_t);
int aplmca_set_sysclk(void *, uint32_t);
int aplmca_open(void *, int);
int aplmca_set_params(void *, int, int,
struct audio_params *, struct audio_params *);
void *aplmca_allocm(void *, int, size_t, int, int);
void aplmca_freem(void *, void *, int);
int aplmca_trigger_output(void *, void *, void *, int,
void (*)(void *), void *, struct audio_params *);
int aplmca_trigger_input(void *, void *, void *, int,
void (*)(void *), void *, struct audio_params *);
int aplmca_halt_output(void *);
int aplmca_halt_input(void *);
const struct audio_hw_if aplmca_hw_if = {
.open = aplmca_open,
.set_params = aplmca_set_params,
.allocm = aplmca_allocm,
.freem = aplmca_freem,
.trigger_output = aplmca_trigger_output,
.trigger_input = aplmca_trigger_input,
.halt_output = aplmca_halt_output,
.halt_input = aplmca_halt_input,
};
int aplmca_match(struct device *, void *, void *);
void aplmca_attach(struct device *, struct device *, void *);
int aplmca_activate(struct device *, int);
const struct cfattach aplmca_ca = {
sizeof (struct aplmca_softc), aplmca_match, aplmca_attach, NULL,
aplmca_activate
};
struct cfdriver aplmca_cd = {
NULL, "aplmca", DV_DULL
};
int
aplmca_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "apple,mca") ||
OF_is_compatible(faa->fa_node, "apple,t8103-mca");
}
void
aplmca_attach(struct device *parent, struct device *self, void *aux)
{
struct aplmca_softc *sc = (struct aplmca_softc *)self;
struct fdt_attach_args *faa = aux;
int i;
if (faa->fa_nreg < 2) {
printf(": no registers\n");
return;
}
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return;
}
if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr,
faa->fa_reg[1].size, 0, &sc->sc_sw_ioh)) {
bus_space_unmap(sc->sc_iot, sc->sc_ioh, faa->fa_reg[0].size);
printf(": can't map registers\n");
return;
}
sc->sc_node = faa->fa_node;
sc->sc_phandle = OF_getpropint(faa->fa_node, "phandle", 0);
sc->sc_nclusters = OF_getpropint(faa->fa_node, "apple,nclusters", 6);
sc->sc_ad = mallocarray(sc->sc_nclusters, sizeof(*sc->sc_ad),
M_DEVBUF, M_WAITOK | M_ZERO);
for (i = 0; i < sc->sc_nclusters; i++) {
sc->sc_ad[i].ad_cluster = i;
sc->sc_ad[i].ad_sc = sc;
sc->sc_ad[i].ad_dai.dd_node = sc->sc_node;
sc->sc_ad[i].ad_dai.dd_cookie = &sc->sc_ad[i];
sc->sc_ad[i].ad_dai.dd_hw_if = &aplmca_hw_if;
sc->sc_ad[i].ad_dai.dd_set_format = aplmca_set_format;
sc->sc_ad[i].ad_dai.dd_set_sysclk = aplmca_set_sysclk;
}
printf("\n");
power_domain_enable_idx(sc->sc_node, 0);
for (i = 0; i < sc->sc_nclusters; i++) {
HCLR4(sc, MCA_SERDES_STATUS(i, MCA_SERDES_TXA),
MCA_SERDES_STATUS_EN);
HCLR4(sc, MCA_SYNCGEN_STATUS(i), MCA_SYNCGEN_STATUS_EN);
HCLR4(sc, MCA_STATUS(i), MCA_STATUS_MCLK_EN);
}
}
int
aplmca_activate(struct device *self, int act)
{
struct aplmca_softc *sc = (struct aplmca_softc *)self;
int i;
switch (act) {
case DVACT_SUSPEND:
for (i = 0; i < sc->sc_nclusters; i++) {
if (sc->sc_ad[i].ad_ac)
power_domain_disable_idx(sc->sc_node, i + 1);
}
power_domain_disable_idx(sc->sc_node, 0);
break;
case DVACT_RESUME:
power_domain_enable_idx(sc->sc_node, 0);
for (i = 0; i < sc->sc_nclusters; i++) {
if (sc->sc_ad[i].ad_ac)
power_domain_enable_idx(sc->sc_node, i + 1);
}
break;
}
return 0;
}
int
aplmca_dai_init(struct aplmca_softc *sc, int port)
{
struct aplmca_dai *ad = &sc->sc_ad[port];
uint32_t conf;
char name[5];
int idx;
snprintf(name, sizeof(name), "tx%da", ad->ad_cluster);
idx = OF_getindex(sc->sc_node, name, "dma-names");
if (idx == -1)
return ENOENT;
ad->ad_ac = apldma_alloc_channel(idx);
if (ad->ad_ac == NULL)
return ENOENT;
power_domain_enable_idx(sc->sc_node, port + 1);
conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA));
conf &= ~MCA_SERDES_CONF_SYNC_SEL_MASK;
conf |= (ad->ad_cluster + 1) << MCA_SERDES_CONF_SYNC_SEL_SHIFT;
conf |= MCA_SERDES_CONF_MAGIC;
HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf);
HWRITE4(sc, MCA_PORT_CLOCK_SEL(port),
(ad->ad_cluster + 1) << MCA_PORT_CLOCK_SEL_SHIFT);
HWRITE4(sc, MCA_PORT_DATA_SEL(port),
MCA_PORT_DATA_SEL_TXA(ad->ad_cluster));
HWRITE4(sc, MCA_PORT_ENABLE(port),
MCA_PORT_ENABLE_CLOCKS | MCA_PORT_ENABLE_TX_DATA);
return 0;
}
void
aplmca_dai_link(struct aplmca_softc *sc, int master, int port)
{
struct aplmca_dai *ad = &sc->sc_ad[master];
HWRITE4(sc, MCA_PORT_CLOCK_SEL(port),
(ad->ad_cluster + 1) << MCA_PORT_CLOCK_SEL_SHIFT);
HWRITE4(sc, MCA_PORT_DATA_SEL(port),
MCA_PORT_DATA_SEL_TXA(ad->ad_cluster));
HWRITE4(sc, MCA_PORT_ENABLE(port),
MCA_PORT_ENABLE_CLOCKS | MCA_PORT_ENABLE_TX_DATA);
}
uint32_t *
aplmca_dai_next_dai(uint32_t *cells)
{
uint32_t phandle = cells[0];
int node, ncells;
node = OF_getnodebyphandle(phandle);
if (node == 0)
return NULL;
ncells = OF_getpropint(node, "#sound-dai-cells", 0);
return cells + ncells + 1;
}
struct dai_device *
aplmca_alloc_cluster(int node)
{
struct aplmca_softc *sc = aplmca_cd.cd_devs[0];
uint32_t *dais;
uint32_t *dai;
uint32_t ports[2];
int nports = 0;
int len, i;
len = OF_getproplen(node, "sound-dai");
if (len != 2 * sizeof(uint32_t) && len != 4 * sizeof(uint32_t))
return NULL;
dais = malloc(len, M_TEMP, M_WAITOK);
OF_getpropintarray(node, "sound-dai", dais, len);
dai = dais;
while (dai && dai < dais + (len / sizeof(uint32_t))) {
if (dai[0] == sc->sc_phandle && nports < nitems(ports))
ports[nports++] = dai[1];
dai = aplmca_dai_next_dai(dai);
}
free(dais, M_TEMP, len);
if (nports == 0)
return NULL;
for (i = 0; i < nports; i++) {
if (ports[i] >= sc->sc_nclusters)
return NULL;
}
if (sc->sc_ad[ports[0]].ad_ac != NULL)
return NULL;
if (aplmca_dai_init(sc, ports[0]))
return NULL;
for (i = 1; i < nports; i++)
aplmca_dai_link(sc, ports[0], ports[i]);
return &sc->sc_ad[ports[0]].ad_dai;
}
int
aplmca_set_format(void *cookie, uint32_t fmt, uint32_t pol,
uint32_t clk)
{
struct aplmca_dai *ad = cookie;
struct aplmca_softc *sc = ad->ad_sc;
uint32_t conf;
conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA));
conf &= ~MCA_SERDES_CONF_WIDTH_MASK;
conf |= MCA_SERDES_CONF_WIDTH_32BIT;
switch (fmt) {
case DAI_FORMAT_I2S:
conf &= ~MCA_SERDES_CONF_BCLK_POL;
break;
case DAI_FORMAT_RJ:
case DAI_FORMAT_LJ:
conf |= MCA_SERDES_CONF_BCLK_POL;
break;
default:
return EINVAL;
}
if (pol & DAI_POLARITY_IB)
conf ^= MCA_SERDES_CONF_BCLK_POL;
if (pol & DAI_POLARITY_IF)
return EINVAL;
if (!(clk & DAI_CLOCK_CBM) || !(clk & DAI_CLOCK_CFM))
return EINVAL;
HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf);
return 0;
}
int
aplmca_set_sysclk(void *cookie, uint32_t rate)
{
struct aplmca_dai *ad = cookie;
struct aplmca_softc *sc = ad->ad_sc;
return clock_set_frequency_idx(sc->sc_node, ad->ad_cluster, rate);
}
int
aplmca_open(void *cookie, int flags)
{
if ((flags & (FWRITE | FREAD)) == (FWRITE | FREAD))
return ENXIO;
return 0;
}
int
aplmca_set_params(void *cookie, int setmode, int usemode,
struct audio_params *play, struct audio_params *rec)
{
if (setmode & AUMODE_PLAY) {
play->sample_rate = 48000;
play->encoding = AUDIO_ENCODING_SLINEAR_LE;
play->precision = 24;
play->bps = 4;
play->msb = 0;
play->channels = 2;
}
return 0;
}
void *
aplmca_allocm(void *cookie, int direction, size_t size, int type,
int flags)
{
struct aplmca_dai *ad = cookie;
if (direction == AUMODE_PLAY) {
ad->ad_pbuf = apldma_allocm(ad->ad_ac, size, flags);
return ad->ad_pbuf;
}
return malloc(size, type, flags | M_ZERO);
}
void
aplmca_freem(void *cookie, void *addr, int type)
{
struct aplmca_dai *ad = cookie;
if (addr == ad->ad_pbuf) {
apldma_freem(ad->ad_ac);
return;
}
free(addr, type, 0);
}
int
aplmca_trigger_output(void *cookie, void *start, void *end, int blksize,
void (*intr)(void *), void *intrarg, struct audio_params *params)
{
struct aplmca_dai *ad = cookie;
struct aplmca_softc *sc = ad->ad_sc;
uint32_t conf, period;
int pad;
if (params->channels > 16)
return EINVAL;
conf = HREAD4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA));
conf &= ~MCA_SERDES_CONF_NSLOTS_MASK;
conf |= ((params->channels - 1) << MCA_SERDES_CONF_NSLOTS_SHIFT);
HWRITE4(sc, MCA_SERDES_CONF(ad->ad_cluster, MCA_SERDES_TXA), conf);
HWRITE4(sc, MCA_SERDES_CHANMASK0(ad->ad_cluster, MCA_SERDES_TXA),
0xffffffff);
HWRITE4(sc, MCA_SERDES_CHANMASK1(ad->ad_cluster, MCA_SERDES_TXA),
0xffffffff << params->channels);
HWRITE4(sc, MCA_SERDES_CHANMASK2(ad->ad_cluster, MCA_SERDES_TXA),
0xffffffff);
HWRITE4(sc, MCA_SERDES_CHANMASK3(ad->ad_cluster, MCA_SERDES_TXA),
0xffffffff << params->channels);
period = params->channels * 32;
HWRITE4(sc, MCA_SYNCGEN_HI_PERIOD(ad->ad_cluster), period - 2);
HWRITE4(sc, MCA_SYNCGEN_LO_PERIOD(ad->ad_cluster), 0);
HWRITE4(sc, MCA_MCLK_CONF(ad->ad_cluster),
1 << MCA_MCLK_CONF_DIV_SHIFT);
clock_enable_idx(sc->sc_node, ad->ad_cluster);
HWRITE4(sc, MCA_SYNCGEN_MCLK_SEL(ad->ad_cluster),
ad->ad_cluster + 1);
HSET4(sc, MCA_STATUS(ad->ad_cluster), MCA_STATUS_MCLK_EN);
HSET4(sc, MCA_SYNCGEN_STATUS(ad->ad_cluster),
MCA_SYNCGEN_STATUS_EN);
HSET4(sc, MCA_SERDES_STATUS(ad->ad_cluster, MCA_SERDES_TXA),
MCA_SERDES_STATUS_EN);
pad = params->bps * 8 - params->precision;
bus_space_write_4(sc->sc_iot, sc->sc_sw_ioh,
MCA_DMA_ADAPTER_A(ad->ad_cluster),
pad << MCA_DMA_ADAPTER_TX_LSB_PAD_SHIFT |
2 << MCA_DMA_ADAPTER_TX_NCHANS_SHIFT |
2 << MCA_DMA_ADAPTER_NCHANS_SHIFT);
return apldma_trigger_output(ad->ad_ac, start, end, blksize,
intr, intrarg, params);
}
int
aplmca_trigger_input(void *cookie, void *start, void *end, int blksize,
void (*intr)(void *), void *intrarg, struct audio_params *params)
{
printf("%s\n", __func__);
return EIO;
}
int
aplmca_halt_output(void *cookie)
{
struct aplmca_dai *ad = cookie;
struct aplmca_softc *sc = ad->ad_sc;
int error;
error = apldma_halt_output(ad->ad_ac);
HCLR4(sc, MCA_SERDES_STATUS(ad->ad_cluster, MCA_SERDES_TXA),
MCA_SERDES_STATUS_EN);
HCLR4(sc, MCA_SYNCGEN_STATUS(ad->ad_cluster),
MCA_SYNCGEN_STATUS_EN);
HCLR4(sc, MCA_STATUS(ad->ad_cluster), MCA_STATUS_MCLK_EN);
clock_disable_idx(sc->sc_node, ad->ad_cluster);
return error;
}
int
aplmca_halt_input(void *cookie)
{
printf("%s\n", __func__);
return 0;
}