Module Name: src Committed By: snj Date: Wed Aug 9 05:49:50 UTC 2017
Modified Files: src/sys/arch/arm/sunxi [netbsd-8]: files.sunxi sun8i_h3_ccu.c sunxi_ccu.h sunxi_ccu_nkmp.c src/sys/arch/evbarm/conf [netbsd-8]: SUNXI Added Files: src/sys/arch/arm/sunxi [netbsd-8]: sun6i_dma.c sun8i_h3_codec.c sunxi_codec.c sunxi_codec.h Log Message: Pull up following revision(s) (requested by jmcneill in ticket #200): sys/arch/arm/sunxi/files.sunxi: 1.15-1.16 sys/arch/arm/sunxi/sun6i_dma.c: 1.1-1.2 sys/arch/arm/sunxi/sun8i_h3_ccu.c: 1.9-1.10 sys/arch/arm/sunxi/sun8i_h3_codec.c: 1.1-1.2 sys/arch/arm/sunxi/sunxi_ccu.h: 1.8 sys/arch/arm/sunxi/sunxi_ccu_nkmp.c: 1.5 sys/arch/arm/sunxi/sunxi_codec.c: 1.1 sys/arch/arm/sunxi/sunxi_codec.h: 1.1 sys/arch/evbarm/conf/SUNXI: 1.20-1.21 Add DMA controller driver for sun6i and later family SoCs. -- Enable sun6idma -- add DMA gate -- Fix burst field encoding, and add a helper function to dump registers from ddb. -- Add support for H3 audio PLL and digital audio part. -- Add support for Allwinner H3 audio codec. -- Enable H3 audio support -- h3_codec_pr_write: clear write mode bit after setting it; fixes an issue with output being mutex when skipping tracks in mpg123 To generate a diff of this commit: cvs rdiff -u -r1.12.4.4 -r1.12.4.5 src/sys/arch/arm/sunxi/files.sunxi cvs rdiff -u -r0 -r1.2.2.2 src/sys/arch/arm/sunxi/sun6i_dma.c \ src/sys/arch/arm/sunxi/sun8i_h3_codec.c cvs rdiff -u -r1.8.4.3 -r1.8.4.4 src/sys/arch/arm/sunxi/sun8i_h3_ccu.c cvs rdiff -u -r1.7.4.3 -r1.7.4.4 src/sys/arch/arm/sunxi/sunxi_ccu.h cvs rdiff -u -r1.4.4.2 -r1.4.4.3 src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c cvs rdiff -u -r0 -r1.1.2.2 src/sys/arch/arm/sunxi/sunxi_codec.c \ src/sys/arch/arm/sunxi/sunxi_codec.h cvs rdiff -u -r1.17.4.4 -r1.17.4.5 src/sys/arch/evbarm/conf/SUNXI Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/arm/sunxi/files.sunxi diff -u src/sys/arch/arm/sunxi/files.sunxi:1.12.4.4 src/sys/arch/arm/sunxi/files.sunxi:1.12.4.5 --- src/sys/arch/arm/sunxi/files.sunxi:1.12.4.4 Wed Jul 26 07:32:06 2017 +++ src/sys/arch/arm/sunxi/files.sunxi Wed Aug 9 05:49:50 2017 @@ -1,4 +1,4 @@ -# $NetBSD: files.sunxi,v 1.12.4.4 2017/07/26 07:32:06 martin Exp $ +# $NetBSD: files.sunxi,v 1.12.4.5 2017/08/09 05:49:50 snj Exp $ # # Configuration info for Allwinner sunxi family SoCs # @@ -107,6 +107,21 @@ device sunxiwdt: sysmon_wdog attach sunxiwdt at fdt with sunxi_wdt file arch/arm/sunxi/sunxi_wdt.c sunxi_wdt +# DMA controller +device sun6idma +attach sun6idma at fdt with sun6i_dma +file arch/arm/sunxi/sun6i_dma.c sun6i_dma + +# Audio codec +device sunxicodec: audiobus, auconv, mulaw, aurateconv +attach sunxicodec at fdt with sunxi_codec +file arch/arm/sunxi/sunxi_codec.c sunxi_codec + +# Audio codec (analog part) +device h3codec +attach h3codec at fdt with h3_codec +file arch/arm/sunxi/sun8i_h3_codec.c h3_codec needs-flag + # SOC parameters defflag opt_soc.h SOC_SUNXI defflag opt_soc.h SOC_SUN8I: SOC_SUNXI Index: src/sys/arch/arm/sunxi/sun8i_h3_ccu.c diff -u src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.8.4.3 src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.8.4.4 --- src/sys/arch/arm/sunxi/sun8i_h3_ccu.c:1.8.4.3 Tue Jul 25 02:03:16 2017 +++ src/sys/arch/arm/sunxi/sun8i_h3_ccu.c Wed Aug 9 05:49:50 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: sun8i_h3_ccu.c,v 1.8.4.3 2017/07/25 02:03:16 snj Exp $ */ +/* $NetBSD: sun8i_h3_ccu.c,v 1.8.4.4 2017/08/09 05:49:50 snj Exp $ */ /*- * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca> @@ -29,7 +29,7 @@ #include <sys/cdefs.h> -__KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu.c,v 1.8.4.3 2017/07/25 02:03:16 snj Exp $"); +__KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu.c,v 1.8.4.4 2017/08/09 05:49:50 snj Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -41,6 +41,7 @@ __KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu #include <arm/sunxi/sunxi_ccu.h> #include <arm/sunxi/sun8i_h3_ccu.h> +#define PLL_AUDIO_CTRL_REG 0x008 #define PLL_PERIPH0_CTRL_REG 0x028 #define AHB1_APB1_CFG_REG 0x054 #define APB2_CFG_REG 0x058 @@ -55,6 +56,7 @@ __KERNEL_RCSID(1, "$NetBSD: sun8i_h3_ccu #define SDMMC2_CLK_REG 0x090 #define USBPHY_CFG_REG 0x0cc #define MBUS_RST_REG 0x0fc +#define AC_DIG_CLK_REG 0x140 #define BUS_SOFT_RST_REG0 0x2c0 #define BUS_SOFT_RST_REG1 0x2c4 #define BUS_SOFT_RST_REG2 0x2c8 @@ -141,6 +143,11 @@ static const char *apb1_parents[] = { "a static const char *apb2_parents[] = { "losc", "hosc", "pll_periph0" }; static const char *mod_parents[] = { "hosc", "pll_periph0", "pll_periph1" }; +static const struct sunxi_ccu_nkmp_tbl sunx8_h3_ac_dig_table[] = { + { 24576000, 13, 0, 0, 13 }, + { 0 } +}; + static struct sunxi_ccu_clk sun8i_h3_ccu_clks[] = { SUNXI_CCU_NKMP(H3_CLK_PLL_PERIPH0, "pll_periph0", "hosc", PLL_PERIPH0_CTRL_REG, /* reg */ @@ -151,6 +158,17 @@ static struct sunxi_ccu_clk sun8i_h3_ccu __BIT(31), /* enable */ SUNXI_CCU_NKMP_DIVIDE_BY_TWO), + SUNXI_CCU_NKMP_TABLE(H3_CLK_PLL_AUDIO_BASE, "pll_audio", "hosc", + PLL_AUDIO_CTRL_REG, /* reg */ + __BITS(14,8), /* n */ + 0, /* k */ + __BITS(4,0), /* m */ + __BITS(19,16), /* p */ + __BIT(31), /* enable */ + __BIT(28), /* lock */ + sunx8_h3_ac_dig_table, /* table */ + 0), + SUNXI_CCU_PREDIV(H3_CLK_AHB1, "ahb1", ahb1_parents, AHB1_APB1_CFG_REG, /* reg */ __BITS(7,6), /* prediv */ @@ -203,6 +221,11 @@ static struct sunxi_ccu_clk sun8i_h3_ccu SUNXI_CCU_PHASE(H3_CLK_MMC2_OUTPUT, "mmc2_output", "mmc2", SDMMC2_CLK_REG, __BITS(10,8)), + SUNXI_CCU_GATE(H3_CLK_AC_DIG, "ac_dig", "pll_audio", + AC_DIG_CLK_REG, 31), + + SUNXI_CCU_GATE(H3_CLK_BUS_DMA, "bus-dma", "ahb1", + BUS_CLK_GATING_REG0, 6), SUNXI_CCU_GATE(H3_CLK_BUS_MMC0, "bus-mmc0", "ahb1", BUS_CLK_GATING_REG0, 8), SUNXI_CCU_GATE(H3_CLK_BUS_MMC1, "bus-mmc1", "ahb1", @@ -230,6 +253,8 @@ static struct sunxi_ccu_clk sun8i_h3_ccu SUNXI_CCU_GATE(H3_CLK_BUS_OHCI3, "bus-ohci3", "ahb2", BUS_CLK_GATING_REG0, 31), + SUNXI_CCU_GATE(H3_CLK_BUS_CODEC, "bus-codec", "apb1", + BUS_CLK_GATING_REG2, 0), SUNXI_CCU_GATE(H3_CLK_BUS_PIO, "bus-pio", "apb1", BUS_CLK_GATING_REG2, 5), Index: src/sys/arch/arm/sunxi/sunxi_ccu.h diff -u src/sys/arch/arm/sunxi/sunxi_ccu.h:1.7.4.3 src/sys/arch/arm/sunxi/sunxi_ccu.h:1.7.4.4 --- src/sys/arch/arm/sunxi/sunxi_ccu.h:1.7.4.3 Tue Jul 25 02:03:16 2017 +++ src/sys/arch/arm/sunxi/sunxi_ccu.h Wed Aug 9 05:49:50 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: sunxi_ccu.h,v 1.7.4.3 2017/07/25 02:03:16 snj Exp $ */ +/* $NetBSD: sunxi_ccu.h,v 1.7.4.4 2017/08/09 05:49:50 snj Exp $ */ /*- * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca> @@ -79,6 +79,7 @@ const char *sunxi_ccu_gate_get_parent(st [_id] = { \ .type = SUNXI_CCU_GATE, \ .base.name = (_name), \ + .base.flags = CLK_SET_RATE_PARENT, \ .u.gate.parent = (_pname), \ .u.gate.reg = (_reg), \ .u.gate.mask = __BIT(_bit), \ @@ -86,6 +87,14 @@ const char *sunxi_ccu_gate_get_parent(st .get_parent = sunxi_ccu_gate_get_parent, \ } +struct sunxi_ccu_nkmp_tbl { + u_int rate; + uint32_t n; + uint32_t k; + uint32_t m; + uint32_t p; +}; + struct sunxi_ccu_nkmp { bus_size_t reg; const char *parent; @@ -96,6 +105,7 @@ struct sunxi_ccu_nkmp { uint32_t lock; uint32_t enable; uint32_t flags; + const struct sunxi_ccu_nkmp_tbl *table; #define SUNXI_CCU_NKMP_DIVIDE_BY_TWO __BIT(0) #define SUNXI_CCU_NKMP_FACTOR_N_EXACT __BIT(1) }; @@ -109,8 +119,8 @@ int sunxi_ccu_nkmp_set_rate(struct sunxi const char *sunxi_ccu_nkmp_get_parent(struct sunxi_ccu_softc *, struct sunxi_ccu_clk *); -#define SUNXI_CCU_NKMP(_id, _name, _parent, _reg, _n, _k, _m, \ - _p, _enable, _flags) \ +#define SUNXI_CCU_NKMP_TABLE(_id, _name, _parent, _reg, _n, _k, _m, \ + _p, _enable, _lock, _tbl, _flags) \ [_id] = { \ .type = SUNXI_CCU_NKMP, \ .base.name = (_name), \ @@ -122,12 +132,20 @@ const char *sunxi_ccu_nkmp_get_parent(st .u.nkmp.p = (_p), \ .u.nkmp.enable = (_enable), \ .u.nkmp.flags = (_flags), \ + .u.nkmp.lock = (_lock), \ + .u.nkmp.table = (_tbl), \ .enable = sunxi_ccu_nkmp_enable, \ .get_rate = sunxi_ccu_nkmp_get_rate, \ .set_rate = sunxi_ccu_nkmp_set_rate, \ .get_parent = sunxi_ccu_nkmp_get_parent, \ } +#define SUNXI_CCU_NKMP(_id, _name, _parent, _reg, _n, _k, _m, \ + _p, _enable, _flags) \ + SUNXI_CCU_NKMP_TABLE(_id, _name, _parent, _reg, _n, _k, _m, \ + _p, _enable, 0, NULL, _flags) + + struct sunxi_ccu_nm { bus_size_t reg; const char **parents; Index: src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c diff -u src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c:1.4.4.2 src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c:1.4.4.3 --- src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c:1.4.4.2 Tue Jul 18 19:13:08 2017 +++ src/sys/arch/arm/sunxi/sunxi_ccu_nkmp.c Wed Aug 9 05:49:50 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: sunxi_ccu_nkmp.c,v 1.4.4.2 2017/07/18 19:13:08 snj Exp $ */ +/* $NetBSD: sunxi_ccu_nkmp.c,v 1.4.4.3 2017/08/09 05:49:50 snj Exp $ */ /*- * Copyright (c) 2017 Jared McNeill <jmcne...@invisible.ca> @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_nkmp.c,v 1.4.4.2 2017/07/18 19:13:08 snj Exp $"); +__KERNEL_RCSID(0, "$NetBSD: sunxi_ccu_nkmp.c,v 1.4.4.3 2017/08/09 05:49:50 snj Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -42,6 +42,7 @@ sunxi_ccu_nkmp_enable(struct sunxi_ccu_s { struct sunxi_ccu_nkmp *nkmp = &clk->u.nkmp; uint32_t val; + int retry; KASSERT(clk->type == SUNXI_CCU_NKMP); @@ -55,6 +56,17 @@ sunxi_ccu_nkmp_enable(struct sunxi_ccu_s val &= ~nkmp->enable; CCU_WRITE(sc, nkmp->reg, val); + if (enable && nkmp->lock) { + for (retry = 1000; retry > 0; retry--) { + val = CCU_READ(sc, nkmp->reg); + if (val & nkmp->lock) + break; + delay(100); + } + if (retry == 0) + return ETIMEDOUT; + } + return 0; } @@ -115,7 +127,41 @@ int sunxi_ccu_nkmp_set_rate(struct sunxi_ccu_softc *sc, struct sunxi_ccu_clk *clk, u_int rate) { - return EIO; + struct sunxi_ccu_nkmp *nkmp = &clk->u.nkmp; + const struct sunxi_ccu_nkmp_tbl *tab; + uint32_t val; + + KASSERT(clk->type == SUNXI_CCU_NKMP); + + if (nkmp->table == NULL || rate == 0) + return EIO; + + for (tab = nkmp->table; tab->rate > 0; tab++) + if (tab->rate == rate) + break; + if (tab->rate == 0) + return EINVAL; + + val = CCU_READ(sc, nkmp->reg); + if (nkmp->n) { + val &= ~nkmp->n; + val |= __SHIFTIN(tab->n, nkmp->n); + } + if (nkmp->k) { + val &= ~nkmp->k; + val |= __SHIFTIN(tab->k, nkmp->k); + } + if (nkmp->m) { + val &= ~nkmp->m; + val |= __SHIFTIN(tab->m, nkmp->m); + } + if (nkmp->p) { + val &= ~nkmp->p; + val |= __SHIFTIN(tab->p, nkmp->p); + } + CCU_WRITE(sc, nkmp->reg, val); + + return 0; } const char * Index: src/sys/arch/evbarm/conf/SUNXI diff -u src/sys/arch/evbarm/conf/SUNXI:1.17.4.4 src/sys/arch/evbarm/conf/SUNXI:1.17.4.5 --- src/sys/arch/evbarm/conf/SUNXI:1.17.4.4 Wed Jul 26 07:32:06 2017 +++ src/sys/arch/evbarm/conf/SUNXI Wed Aug 9 05:49:50 2017 @@ -1,5 +1,5 @@ # -# $NetBSD: SUNXI,v 1.17.4.4 2017/07/26 07:32:06 martin Exp $ +# $NetBSD: SUNXI,v 1.17.4.5 2017/08/09 05:49:50 snj Exp $ # # Allwinner sunxi family # @@ -89,6 +89,7 @@ armgic0 at gic? # Memory controller # DMA controller +sun6idma* at fdt? # DMA controller # Clock and Reset controller @@ -114,6 +115,12 @@ iic* at i2cbus? # RTC sunxirtc* at fdt? # RTC +# Audio +sunxicodec* at fdt? # Audio codec +h3codec* at fdt? # H3 audio codec (analog part) +audio* at audiobus? +spkr* at audio? + # SDMMC sunximmc* at fdt? # SDMMC sdmmc* at sunximmc? Added files: Index: src/sys/arch/arm/sunxi/sun6i_dma.c diff -u /dev/null src/sys/arch/arm/sunxi/sun6i_dma.c:1.2.2.2 --- /dev/null Wed Aug 9 05:49:50 2017 +++ src/sys/arch/arm/sunxi/sun6i_dma.c Wed Aug 9 05:49:50 2017 @@ -0,0 +1,453 @@ +/* $NetBSD: sun6i_dma.c,v 1.2.2.2 2017/08/09 05:49:50 snj Exp $ */ + +/*- + * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_ddb.h" + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sun6i_dma.c,v 1.2.2.2 2017/08/09 05:49:50 snj Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/mutex.h> +#include <sys/bitops.h> +#include <sys/kmem.h> + +#include <dev/fdt/fdtvar.h> + +#define DMA_IRQ_EN_REG0_REG 0x0000 +#define DMA_IRQ_EN_REG1_REG 0x0004 +#define DMA_IRQ_EN_REG0_QUEUE_IRQ_EN(n) __BIT(n * 4 + 2) +#define DMA_IRQ_EN_REG0_PKG_IRQ_EN(n) __BIT(n * 4 + 1) +#define DMA_IRQ_EN_REG0_HLAF_IRQ_EN(n) __BIT(n * 4 + 0) +#define DMA_IRQ_EN_REG1_QUEUE_IRQ_EN(n) __BIT((n - 8) * 4 + 2) +#define DMA_IRQ_EN_REG1_PKG_IRQ_EN(n) __BIT((n - 8) * 4 + 1) +#define DMA_IRQ_EN_REG1_HLAF_IRQ_EN(n) __BIT((n - 8) * 4 + 0) +#define DMA_IRQ_PEND_REG0_REG 0x0010 +#define DMA_IRQ_PEND_REG1_REG 0x0014 +#define DMA_IRQ_QUEUE_MASK 0x4444444444444444ULL +#define DMA_IRQ_PKG_MASK 0x2222222222222222ULL +#define DMA_IRQ_HF_MASK 0x1111111111111111ULL +#define DMA_STA_REG 0x0030 +#define DMA_EN_REG(n) (0x0100 + (n) * 0x40 + 0x00) +#define DMA_EN_EN __BIT(0) +#define DMA_PAU_REG(n) (0x0100 + (n) * 0x40 + 0x04) +#define DMA_PAU_PAUSE __BIT(0) +#define DMA_START_ADDR_REG(n) (0x0100 + (n) * 0x40 + 0x08) +#define DMA_CFG_REG(n) (0x0100 + (n) * 0x40 + 0x0C) +#define DMA_CFG_DEST_DATA_WIDTH __BITS(26,25) +#define DMA_CFG_DATA_WIDTH(n) ((n) >> 4) +#define DMA_CFG_DEST_BST_LEN __BITS(24,23) +#define DMA_CFG_BST_LEN(n) ((n) == 1 ? 0 : (((n) >> 3) + 1)) +#define DMA_CFG_DEST_ADDR_MODE __BITS(22,21) +#define DMA_CFG_ADDR_MODE_LINEAR 0 +#define DMA_CFG_ADDR_MODE_IO 1 +#define DMA_CFG_DEST_DRQ_TYPE __BITS(20,16) +#define DMA_CFG_DRQ_TYPE_SDRAM 1 +#define DMA_CFG_SRC_DATA_WIDTH __BITS(10,9) +#define DMA_CFG_SRC_BST_LEN __BITS(8,7) +#define DMA_CFG_SRC_ADDR_MODE __BITS(6,5) +#define DMA_CFG_SRC_DRQ_TYPE __BITS(4,0) +#define DMA_CUR_SRC_REG(n) (0x0100 + (n) * 0x40 + 0x10) +#define DMA_CUR_DEST_REG(n) (0x0100 + (n) * 0x40 + 0x14) +#define DMA_BCNT_LEFT_REG(n) (0x0100 + (n) * 0x40 + 0x18) +#define DMA_PARA_REG(n) (0x0100 + (n) * 0x40 + 0x1C) +#define DMA_PARA_DATA_BLK_SIZE __BITS(15,8) +#define DMA_PARA_WAIT_CYC __BITS(7,0) + +struct sun6idma_desc { + uint32_t dma_config; + uint32_t dma_srcaddr; + uint32_t dma_dstaddr; + uint32_t dma_bcnt; + uint32_t dma_para; + uint32_t dma_next; +#define DMA_NULL 0xfffff800 +}; + +static const struct of_compat_data compat_data[] = { + { "allwinner,sun6i-a31-dma", 16 }, + { "allwinner,sun8i-a83t-dma", 8 }, + { "allwinner,sun8i-h3-dma", 12 }, + { NULL } +}; + +struct sun6idma_channel { + uint8_t ch_index; + void (*ch_callback)(void *); + void *ch_callbackarg; + u_int ch_portid; + + bus_dma_segment_t ch_dmasegs[1]; + bus_dmamap_t ch_dmamap; + void *ch_dmadesc; + bus_size_t ch_dmadesclen; +}; + +struct sun6idma_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + bus_dma_tag_t sc_dmat; + int sc_phandle; + void *sc_ih; + + kmutex_t sc_lock; + + struct sun6idma_channel *sc_chan; + u_int sc_nchan; +}; + +#define DMA_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define DMA_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +static void * +sun6idma_acquire(device_t dev, const void *data, size_t len, + void (*cb)(void *), void *cbarg) +{ + struct sun6idma_softc *sc = device_private(dev); + struct sun6idma_channel *ch = NULL; + uint32_t irqen; + uint8_t index; + + if (len != 4) + return NULL; + + const u_int portid = be32dec(data); + if (portid > __SHIFTOUT_MASK(DMA_CFG_SRC_DRQ_TYPE)) + return NULL; + + mutex_enter(&sc->sc_lock); + + for (index = 0; index < sc->sc_nchan; index++) { + if (sc->sc_chan[index].ch_callback == NULL) { + ch = &sc->sc_chan[index]; + ch->ch_callback = cb; + ch->ch_callbackarg = cbarg; + ch->ch_portid = portid; + + irqen = DMA_READ(sc, index < 8 ? + DMA_IRQ_EN_REG0_REG : + DMA_IRQ_EN_REG1_REG); + irqen |= (index < 8 ? + DMA_IRQ_EN_REG0_PKG_IRQ_EN(index) : + DMA_IRQ_EN_REG1_PKG_IRQ_EN(index)); + DMA_WRITE(sc, index < 8 ? + DMA_IRQ_EN_REG0_REG : + DMA_IRQ_EN_REG1_REG, irqen); + + break; + } + } + + mutex_exit(&sc->sc_lock); + + return ch; +} + +static void +sun6idma_release(device_t dev, void *priv) +{ + struct sun6idma_softc *sc = device_private(dev); + struct sun6idma_channel *ch = priv; + uint32_t irqen; + uint8_t index = ch->ch_index; + + mutex_enter(&sc->sc_lock); + + irqen = DMA_READ(sc, index < 8 ? + DMA_IRQ_EN_REG0_REG : + DMA_IRQ_EN_REG1_REG); + irqen &= ~(index < 8 ? + DMA_IRQ_EN_REG0_PKG_IRQ_EN(index) : + DMA_IRQ_EN_REG1_PKG_IRQ_EN(index)); + DMA_WRITE(sc, index < 8 ? + DMA_IRQ_EN_REG0_REG : + DMA_IRQ_EN_REG1_REG, irqen); + + ch->ch_callback = NULL; + ch->ch_callbackarg = NULL; + + mutex_exit(&sc->sc_lock); +} + +static int +sun6idma_transfer(device_t dev, void *priv, struct fdtbus_dma_req *req) +{ + struct sun6idma_softc *sc = device_private(dev); + struct sun6idma_channel *ch = priv; + struct sun6idma_desc *desc = ch->ch_dmadesc; + uint32_t src, dst, len, cfg, mem_cfg, dev_cfg; + uint32_t mem_width, dev_width, mem_burst, dev_burst; + + if (req->dreq_nsegs != 1) + return EINVAL; + + mem_width = DMA_CFG_DATA_WIDTH(req->dreq_mem_opt.opt_bus_width); + dev_width = DMA_CFG_DATA_WIDTH(req->dreq_dev_opt.opt_bus_width); + mem_burst = DMA_CFG_BST_LEN(req->dreq_mem_opt.opt_burst_len); + dev_burst = DMA_CFG_BST_LEN(req->dreq_dev_opt.opt_burst_len); + + mem_cfg = __SHIFTIN(mem_width, DMA_CFG_SRC_DATA_WIDTH) | + __SHIFTIN(mem_burst, DMA_CFG_SRC_BST_LEN) | + __SHIFTIN(DMA_CFG_ADDR_MODE_LINEAR, DMA_CFG_SRC_ADDR_MODE) | + __SHIFTIN(DMA_CFG_DRQ_TYPE_SDRAM, DMA_CFG_SRC_DRQ_TYPE); + dev_cfg = __SHIFTIN(dev_width, DMA_CFG_SRC_DATA_WIDTH) | + __SHIFTIN(dev_burst, DMA_CFG_SRC_BST_LEN) | + __SHIFTIN(DMA_CFG_ADDR_MODE_IO, DMA_CFG_SRC_ADDR_MODE) | + __SHIFTIN(ch->ch_portid, DMA_CFG_SRC_DRQ_TYPE); + + if (req->dreq_dir == FDT_DMA_READ) { + src = req->dreq_dev_phys; + dst = req->dreq_segs[0].ds_addr; + cfg = mem_cfg << 16 | dev_cfg; + } else { + src = req->dreq_segs[0].ds_addr; + dst = req->dreq_dev_phys; + cfg = dev_cfg << 16 | mem_cfg; + } + len = req->dreq_segs[0].ds_len; + + desc->dma_config = htole32(cfg); + desc->dma_srcaddr = htole32(src); + desc->dma_dstaddr = htole32(dst); + desc->dma_bcnt = htole32(len); + desc->dma_para = htole32(0); + desc->dma_next = htole32(DMA_NULL); + + bus_dmamap_sync(sc->sc_dmat, ch->ch_dmamap, 0, ch->ch_dmadesclen, + BUS_DMASYNC_PREWRITE); + + DMA_WRITE(sc, DMA_START_ADDR_REG(ch->ch_index), + ch->ch_dmamap->dm_segs[0].ds_addr); + DMA_WRITE(sc, DMA_EN_REG(ch->ch_index), DMA_EN_EN); + + if ((DMA_READ(sc, DMA_EN_REG(ch->ch_index)) & DMA_EN_EN) == 0) { + aprint_error_dev(sc->sc_dev, + "DMA Channel %u failed to start\n", ch->ch_index); + return EIO; + } + + return 0; +} + +static void +sun6idma_halt(device_t dev, void *priv) +{ + struct sun6idma_softc *sc = device_private(dev); + struct sun6idma_channel *ch = priv; + + DMA_WRITE(sc, DMA_EN_REG(ch->ch_index), 0); +} + +static const struct fdtbus_dma_controller_func sun6idma_funcs = { + .acquire = sun6idma_acquire, + .release = sun6idma_release, + .transfer = sun6idma_transfer, + .halt = sun6idma_halt +}; + +static int +sun6idma_intr(void *priv) +{ + struct sun6idma_softc *sc = priv; + uint32_t pend0, pend1, bit; + uint64_t pend, mask; + uint8_t index; + + pend0 = DMA_READ(sc, DMA_IRQ_PEND_REG0_REG); + pend1 = DMA_READ(sc, DMA_IRQ_PEND_REG1_REG); + if (!pend0 && !pend1) + return 0; + + DMA_WRITE(sc, DMA_IRQ_PEND_REG0_REG, pend0); + DMA_WRITE(sc, DMA_IRQ_PEND_REG1_REG, pend1); + + pend = pend0 | ((uint64_t)pend1 << 32); + + while ((bit = ffs64(pend & DMA_IRQ_PKG_MASK)) != 0) { + mask = __BIT(bit - 1); + pend &= ~mask; + index = (bit - 1) / 4; + + if (sc->sc_chan[index].ch_callback == NULL) + continue; + sc->sc_chan[index].ch_callback( + sc->sc_chan[index].ch_callbackarg); + } + + return 1; +} + +static int +sun6idma_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compat_data(faa->faa_phandle, compat_data); +} + +static void +sun6idma_attach(device_t parent, device_t self, void *aux) +{ + struct sun6idma_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + const size_t desclen = sizeof(struct sun6idma_desc); + struct fdtbus_reset *rst; + struct clk *clk; + char intrstr[128]; + bus_addr_t addr; + bus_size_t size; + int error, nsegs; + u_int index; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + if ((clk = fdtbus_clock_get_index(phandle, 0)) == NULL || + clk_enable(clk) != 0) { + aprint_error(": couldn't enable clock\n"); + return; + } + if ((rst = fdtbus_reset_get_index(phandle, 0)) == NULL || + fdtbus_reset_deassert(rst) != 0) { + aprint_error(": couldn't de-assert reset\n"); + return; + } + + sc->sc_dev = self; + sc->sc_phandle = phandle; + sc->sc_dmat = faa->faa_dmat; + sc->sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SCHED); + + if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { + aprint_error(": failed to decode interrupt\n"); + return; + } + + sc->sc_nchan = of_search_compatible(phandle, compat_data)->data; + sc->sc_chan = kmem_alloc(sizeof(*sc->sc_chan) * sc->sc_nchan, KM_SLEEP); + + aprint_naive("\n"); + aprint_normal(": DMA controller (%u channels)\n", sc->sc_nchan); + + DMA_WRITE(sc, DMA_IRQ_EN_REG0_REG, 0); + DMA_WRITE(sc, DMA_IRQ_EN_REG1_REG, 0); + DMA_WRITE(sc, DMA_IRQ_PEND_REG0_REG, ~0); + DMA_WRITE(sc, DMA_IRQ_PEND_REG1_REG, ~0); + + for (index = 0; index < sc->sc_nchan; index++) { + struct sun6idma_channel *ch = &sc->sc_chan[index]; + ch->ch_index = index; + ch->ch_callback = NULL; + ch->ch_callbackarg = NULL; + ch->ch_dmadesclen = desclen; + + error = bus_dmamem_alloc(sc->sc_dmat, desclen, 0, 0, + ch->ch_dmasegs, 1, &nsegs, BUS_DMA_WAITOK); + if (error) + panic("bus_dmamem_alloc failed: %d", error); + error = bus_dmamem_map(sc->sc_dmat, ch->ch_dmasegs, nsegs, + desclen, &ch->ch_dmadesc, BUS_DMA_WAITOK); + if (error) + panic("bus_dmamem_map failed: %d", error); + error = bus_dmamap_create(sc->sc_dmat, desclen, 1, desclen, 0, + BUS_DMA_WAITOK, &ch->ch_dmamap); + if (error) + panic("bus_dmamap_create failed: %d", error); + error = bus_dmamap_load(sc->sc_dmat, ch->ch_dmamap, + ch->ch_dmadesc, desclen, NULL, BUS_DMA_WAITOK); + if (error) + panic("bus_dmamap_load failed: %d", error); + + DMA_WRITE(sc, DMA_EN_REG(index), 0); + } + + sc->sc_ih = fdtbus_intr_establish(phandle, 0, IPL_SCHED, FDT_INTR_MPSAFE, + sun6idma_intr, sc); + if (sc->sc_ih == NULL) { + aprint_error_dev(sc->sc_dev, + "couldn't establish interrupt on %s\n", intrstr); + return; + } + aprint_normal_dev(sc->sc_dev, "interrupting on %s\n", intrstr); + + fdtbus_register_dma_controller(self, phandle, &sun6idma_funcs); +} + +CFATTACH_DECL_NEW(sun6i_dma, sizeof(struct sun6idma_softc), + sun6idma_match, sun6idma_attach, NULL, NULL); + +#ifdef DDB +void sun6idma_dump(void); + +void +sun6idma_dump(void) +{ + struct sun6idma_softc *sc; + device_t dev; + u_int index; + + dev = device_find_by_driver_unit("sun6idma", 0); + if (dev == NULL) + return; + sc = device_private(dev); + + device_printf(dev, "DMA_IRQ_EN_REG0_REG: %08x\n", DMA_READ(sc, DMA_IRQ_EN_REG0_REG)); + device_printf(dev, "DMA_IRQ_EN_REG1_REG: %08x\n", DMA_READ(sc, DMA_IRQ_EN_REG1_REG)); + device_printf(dev, "DMA_IRQ_PEND_REG0_REG: %08x\n", DMA_READ(sc, DMA_IRQ_PEND_REG0_REG)); + device_printf(dev, "DMA_IRQ_PEND_REG1_REG: %08x\n", DMA_READ(sc, DMA_IRQ_PEND_REG1_REG)); + device_printf(dev, "DMA_STA_REG: %08x\n", DMA_READ(sc, DMA_STA_REG)); + + for (index = 0; index < sc->sc_nchan; index++) { + struct sun6idma_channel *ch = &sc->sc_chan[index]; + if (ch->ch_callback == NULL) + continue; + device_printf(dev, " %2d: DMA_EN_REG: %08x\n", index, DMA_READ(sc, DMA_EN_REG(index))); + device_printf(dev, " %2d: DMA_PAU_REG: %08x\n", index, DMA_READ(sc, DMA_PAU_REG(index))); + device_printf(dev, " %2d: DMA_START_ADDR_REG: %08x\n", index, DMA_READ(sc, DMA_START_ADDR_REG(index))); + device_printf(dev, " %2d: DMA_CFG_REG: %08x\n", index, DMA_READ(sc, DMA_CFG_REG(index))); + device_printf(dev, " %2d: DMA_CUR_SRC_REG: %08x\n", index, DMA_READ(sc, DMA_CUR_SRC_REG(index))); + device_printf(dev, " %2d: DMA_CUR_DEST_REG: %08x\n", index, DMA_READ(sc, DMA_CUR_DEST_REG(index))); + device_printf(dev, " %2d: DMA_BCNT_LEFT_REG: %08x\n", index, DMA_READ(sc, DMA_BCNT_LEFT_REG(index))); + device_printf(dev, " %2d: DMA_PARA_REG: %08x\n", index, DMA_READ(sc, DMA_PARA_REG(index))); + } +} +#endif Index: src/sys/arch/arm/sunxi/sun8i_h3_codec.c diff -u /dev/null src/sys/arch/arm/sunxi/sun8i_h3_codec.c:1.2.2.2 --- /dev/null Wed Aug 9 05:49:50 2017 +++ src/sys/arch/arm/sunxi/sun8i_h3_codec.c Wed Aug 9 05:49:50 2017 @@ -0,0 +1,475 @@ +/* $NetBSD: sun8i_h3_codec.c,v 1.2.2.2 2017/08/09 05:49:50 snj Exp $ */ + +/*- + * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sun8i_h3_codec.c,v 1.2.2.2 2017/08/09 05:49:50 snj Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/device.h> +#include <sys/kmem.h> +#include <sys/bitops.h> + +#include <sys/audioio.h> +#include <dev/audio_if.h> + +#include <arm/sunxi/sunxi_codec.h> + +#define H3_PR_CFG 0x00 +#define H3_AC_PR_RW __BIT(24) +#define H3_AC_PR_RST __BIT(18) +#define H3_AC_PR_ADDR __BITS(20,16) +#define H3_ACDA_PR_WDAT __BITS(15,8) +#define H3_ACDA_PR_RDAT __BITS(7,0) + +#define H3_LOMIXSC 0x01 +#define H3_LOMIXSC_LDAC __BIT(1) +#define H3_ROMIXSC 0x02 +#define H3_ROMIXSC_RDAC __BIT(1) +#define H3_DAC_PA_SRC 0x03 +#define H3_DACAREN __BIT(7) +#define H3_DACALEN __BIT(6) +#define H3_RMIXEN __BIT(5) +#define H3_LMIXEN __BIT(4) +#define H3_LINEIN_GCTR 0x05 +#define H3_LINEING __BITS(6,4) +#define H3_MIC_GCTR 0x06 +#define H3_MIC1_GAIN __BITS(6,4) +#define H3_MIC2_GAIN __BITS(2,0) +#define H3_PAEN_CTR 0x07 +#define H3_LINEOUTEN __BIT(7) +#define H3_LINEOUT_VOLC 0x09 +#define H3_LINEOUTVOL __BITS(7,3) +#define H3_MIC2G_LINEOUT_CTR 0x0a +#define H3_LINEOUT_LSEL __BIT(3) +#define H3_LINEOUT_RSEL __BIT(2) +#define H3_LADCMIXSC 0x0c +#define H3_RADCMIXSC 0x0d +#define H3_ADCMIXSC_MIC1 __BIT(6) +#define H3_ADCMIXSC_MIC2 __BIT(5) +#define H3_ADCMIXSC_LINEIN __BIT(2) +#define H3_ADCMIXSC_OMIXER __BITS(1,0) +#define H3_ADC_AP_EN 0x0f +#define H3_ADCREN __BIT(7) +#define H3_ADCLEN __BIT(6) +#define H3_ADCG __BITS(2,0) + +struct h3_codec_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + int sc_phandle; +}; + +enum h3_codec_mixer_ctrl { + H3_CODEC_OUTPUT_CLASS, + H3_CODEC_INPUT_CLASS, + H3_CODEC_RECORD_CLASS, + + H3_CODEC_OUTPUT_MASTER_VOLUME, + H3_CODEC_INPUT_DAC_VOLUME, + H3_CODEC_INPUT_LINEIN_VOLUME, + H3_CODEC_INPUT_MIC1_VOLUME, + H3_CODEC_INPUT_MIC2_VOLUME, + H3_CODEC_RECORD_AGC_VOLUME, + H3_CODEC_RECORD_SOURCE, + + H3_CODEC_MIXER_CTRL_LAST +}; + +static const struct h3_codec_mixer { + const char * name; + enum h3_codec_mixer_ctrl mixer_class; + u_int reg; + u_int mask; +} h3_codec_mixers[H3_CODEC_MIXER_CTRL_LAST] = { + [H3_CODEC_OUTPUT_MASTER_VOLUME] = { AudioNmaster, + H3_CODEC_OUTPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL }, + [H3_CODEC_INPUT_DAC_VOLUME] = { AudioNdac, + H3_CODEC_INPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL }, + [H3_CODEC_INPUT_LINEIN_VOLUME] = { AudioNline, + H3_CODEC_INPUT_CLASS, H3_LINEIN_GCTR, H3_LINEING }, + [H3_CODEC_INPUT_MIC1_VOLUME] = { "mic1", + H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC1_GAIN }, + [H3_CODEC_INPUT_MIC2_VOLUME] = { "mic2", + H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC2_GAIN }, + [H3_CODEC_RECORD_AGC_VOLUME] = { AudioNagc, + H3_CODEC_RECORD_CLASS, H3_ADC_AP_EN, H3_ADCG }, +}; + +#define RD4(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define WR4(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +static struct h3_codec_softc * +h3_codec_find(int phandle) +{ + struct h3_codec_softc *csc; + device_t dev; + + dev = device_find_by_driver_unit("h3codec", 0); + if (dev == NULL) + return NULL; + csc = device_private(dev); + if (csc->sc_phandle != phandle) + return NULL; + + return csc; +} + +static u_int +h3_codec_pr_read(struct h3_codec_softc *csc, u_int addr) +{ + uint32_t val; + + /* Read current value */ + val = RD4(csc, H3_PR_CFG); + + /* De-assert reset */ + val |= H3_AC_PR_RST; + WR4(csc, H3_PR_CFG, val); + + /* Read mode */ + val &= ~H3_AC_PR_RW; + WR4(csc, H3_PR_CFG, val); + + /* Set address */ + val &= ~H3_AC_PR_ADDR; + val |= __SHIFTIN(addr, H3_AC_PR_ADDR); + WR4(csc, H3_PR_CFG, val); + + /* Read data */ + return __SHIFTOUT(RD4(csc, H3_PR_CFG), H3_ACDA_PR_RDAT); +} + +static void +h3_codec_pr_write(struct h3_codec_softc *csc, u_int addr, u_int data) +{ + uint32_t val; + + /* Read current value */ + val = RD4(csc, H3_PR_CFG); + + /* De-assert reset */ + val |= H3_AC_PR_RST; + WR4(csc, H3_PR_CFG, val); + + /* Set address */ + val &= ~H3_AC_PR_ADDR; + val |= __SHIFTIN(addr, H3_AC_PR_ADDR); + WR4(csc, H3_PR_CFG, val); + + /* Write data */ + val &= ~H3_ACDA_PR_WDAT; + val |= __SHIFTIN(data, H3_ACDA_PR_WDAT); + WR4(csc, H3_PR_CFG, val); + + /* Write mode */ + val |= H3_AC_PR_RW; + WR4(csc, H3_PR_CFG, val); + + /* Clear write mode */ + val &= ~H3_AC_PR_RW; + WR4(csc, H3_PR_CFG, val); +} + +static void +h3_codec_pr_set_clear(struct h3_codec_softc *csc, u_int addr, u_int set, u_int clr) +{ + u_int old, new; + + old = h3_codec_pr_read(csc, addr); + new = set | (old & ~clr); + h3_codec_pr_write(csc, addr, new); +} + +static int +h3_codec_init(struct sunxi_codec_softc *sc) +{ + struct h3_codec_softc *csc; + int phandle; + + /* Lookup the codec analog controls phandle */ + phandle = fdtbus_get_phandle(sc->sc_phandle, + "allwinner,codec-analog-controls"); + if (phandle < 0) { + aprint_error_dev(sc->sc_dev, + "missing allwinner,codec-analog-controls property\n"); + return ENXIO; + } + + /* Find a matching h3codec instance */ + sc->sc_codec_priv = h3_codec_find(phandle); + if (sc->sc_codec_priv == NULL) { + aprint_error_dev(sc->sc_dev, "couldn't find codec analog controls\n"); + return ENOENT; + } + csc = sc->sc_codec_priv; + + /* Right & Left LINEOUT enable */ + h3_codec_pr_set_clear(csc, H3_PAEN_CTR, H3_LINEOUTEN, 0); + h3_codec_pr_set_clear(csc, H3_MIC2G_LINEOUT_CTR, + H3_LINEOUT_LSEL | H3_LINEOUT_RSEL, 0); + + return 0; +} + +static void +h3_codec_mute(struct sunxi_codec_softc *sc, int mute, u_int mode) +{ + struct h3_codec_softc * const csc = sc->sc_codec_priv; + + if (mode == AUMODE_PLAY) { + if (mute) { + /* Mute DAC l/r channels to output mixer */ + h3_codec_pr_set_clear(csc, H3_LOMIXSC, + 0, H3_LOMIXSC_LDAC); + h3_codec_pr_set_clear(csc, H3_ROMIXSC, + 0, H3_ROMIXSC_RDAC); + /* Disable DAC analog l/r channels and output mixer */ + h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC, + 0, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN); + } else { + /* Enable DAC analog l/r channels and output mixer */ + h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC, + H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN, 0); + /* Unmute DAC l/r channels to output mixer */ + h3_codec_pr_set_clear(csc, H3_LOMIXSC, H3_LOMIXSC_LDAC, 0); + h3_codec_pr_set_clear(csc, H3_ROMIXSC, H3_ROMIXSC_RDAC, 0); + } + } else { + if (mute) { + /* Disable ADC analog l/r channels */ + h3_codec_pr_set_clear(csc, H3_ADC_AP_EN, + 0, H3_ADCREN | H3_ADCLEN); + } else { + /* Enable ADC analog l/r channels */ + h3_codec_pr_set_clear(csc, H3_ADC_AP_EN, + H3_ADCREN | H3_ADCLEN, 0); + } + } +} + +static int +h3_codec_set_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc) +{ + struct h3_codec_softc * const csc = sc->sc_codec_priv; + const struct h3_codec_mixer *mix; + u_int val, shift; + int nvol; + + switch (mc->dev) { + case H3_CODEC_OUTPUT_MASTER_VOLUME: + case H3_CODEC_INPUT_DAC_VOLUME: + case H3_CODEC_INPUT_LINEIN_VOLUME: + case H3_CODEC_INPUT_MIC1_VOLUME: + case H3_CODEC_INPUT_MIC2_VOLUME: + case H3_CODEC_RECORD_AGC_VOLUME: + mix = &h3_codec_mixers[mc->dev]; + val = h3_codec_pr_read(csc, mix->reg); + shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask)); + nvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] >> shift; + val &= ~mix->mask; + val |= __SHIFTIN(nvol, mix->mask); + h3_codec_pr_write(csc, mix->reg, val); + return 0; + + case H3_CODEC_RECORD_SOURCE: + h3_codec_pr_write(csc, H3_LADCMIXSC, mc->un.mask); + h3_codec_pr_write(csc, H3_RADCMIXSC, mc->un.mask); + return 0; + } + + return ENXIO; +} + +static int +h3_codec_get_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc) +{ + struct h3_codec_softc * const csc = sc->sc_codec_priv; + const struct h3_codec_mixer *mix; + u_int val, shift; + int nvol; + + switch (mc->dev) { + case H3_CODEC_OUTPUT_MASTER_VOLUME: + case H3_CODEC_INPUT_DAC_VOLUME: + case H3_CODEC_INPUT_LINEIN_VOLUME: + case H3_CODEC_INPUT_MIC1_VOLUME: + case H3_CODEC_INPUT_MIC2_VOLUME: + case H3_CODEC_RECORD_AGC_VOLUME: + mix = &h3_codec_mixers[mc->dev]; + val = h3_codec_pr_read(csc, mix->reg); + shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask)); + nvol = __SHIFTOUT(val, mix->mask) << shift; + mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = nvol; + mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = nvol; + return 0; + + case H3_CODEC_RECORD_SOURCE: + mc->un.mask = + h3_codec_pr_read(csc, H3_LADCMIXSC) | + h3_codec_pr_read(csc, H3_RADCMIXSC); + return 0; + } + + return ENXIO; +} + +static int +h3_codec_query_devinfo(struct sunxi_codec_softc *sc, mixer_devinfo_t *di) +{ + const struct h3_codec_mixer *mix; + + switch (di->index) { + case H3_CODEC_OUTPUT_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCoutputs); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case H3_CODEC_INPUT_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCinputs); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case H3_CODEC_RECORD_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCrecord); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case H3_CODEC_OUTPUT_MASTER_VOLUME: + case H3_CODEC_INPUT_DAC_VOLUME: + case H3_CODEC_INPUT_LINEIN_VOLUME: + case H3_CODEC_INPUT_MIC1_VOLUME: + case H3_CODEC_INPUT_MIC2_VOLUME: + case H3_CODEC_RECORD_AGC_VOLUME: + mix = &h3_codec_mixers[di->index]; + di->mixer_class = mix->mixer_class; + strcpy(di->label.name, mix->name); + di->un.v.delta = + 256 / (__SHIFTOUT_MASK(mix->mask) + 1); + di->type = AUDIO_MIXER_VALUE; + di->next = di->prev = AUDIO_MIXER_LAST; + di->un.v.num_channels = 2; + strcpy(di->un.v.units.name, AudioNvolume); + return 0; + + case H3_CODEC_RECORD_SOURCE: + di->mixer_class = H3_CODEC_RECORD_CLASS; + strcpy(di->label.name, AudioNsource); + di->type = AUDIO_MIXER_SET; + di->next = di->prev = AUDIO_MIXER_LAST; + di->un.s.num_mem = 4; + strcpy(di->un.s.member[0].label.name, AudioNline); + di->un.s.member[0].mask = H3_ADCMIXSC_LINEIN; + strcpy(di->un.s.member[1].label.name, "mic1"); + di->un.s.member[1].mask = H3_ADCMIXSC_MIC1; + strcpy(di->un.s.member[2].label.name, "mic2"); + di->un.s.member[2].mask = H3_ADCMIXSC_MIC2; + strcpy(di->un.s.member[3].label.name, AudioNdac); + di->un.s.member[3].mask = H3_ADCMIXSC_OMIXER; + return 0; + + } + + return ENXIO; +} + +const struct sunxi_codec_conf sun8i_h3_codecconf = { + .name = "H3 Audio Codec", + + .init = h3_codec_init, + .mute = h3_codec_mute, + .set_port = h3_codec_set_port, + .get_port = h3_codec_get_port, + .query_devinfo = h3_codec_query_devinfo, + + .DPC = 0x00, + .DAC_FIFOC = 0x04, + .DAC_FIFOS = 0x08, + .DAC_TXDATA = 0x20, + .ADC_FIFOC = 0x10, + .ADC_FIFOS = 0x14, + .ADC_RXDATA = 0x18, + .DAC_CNT = 0x40, + .ADC_CNT = 0x44, +}; + +/* + * Device glue, only here to claim resources on behalf of the sunxi_codec driver. + */ + +static const char * compatible[] = { + "allwinner,sun8i-h3-codec-analog", + NULL +}; + +static int +h3_codec_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +h3_codec_attach(device_t parent, device_t self, void *aux) +{ + struct h3_codec_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + bus_addr_t addr; + bus_size_t size; + + sc->sc_dev = self; + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + sc->sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + + sc->sc_phandle = phandle; + + aprint_naive("\n"); + aprint_normal(": H3 Audio Codec (analog part)\n"); +} + +CFATTACH_DECL_NEW(h3_codec, sizeof(struct h3_codec_softc), + h3_codec_match, h3_codec_attach, NULL, NULL); Index: src/sys/arch/arm/sunxi/sunxi_codec.c diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_codec.c:1.1.2.2 --- /dev/null Wed Aug 9 05:49:50 2017 +++ src/sys/arch/arm/sunxi/sunxi_codec.c Wed Aug 9 05:49:50 2017 @@ -0,0 +1,751 @@ +/* $NetBSD: sunxi_codec.c,v 1.1.2.2 2017/08/09 05:49:50 snj Exp $ */ + +/*- + * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_ddb.h" + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sunxi_codec.c,v 1.1.2.2 2017/08/09 05:49:50 snj Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/device.h> +#include <sys/kmem.h> +#include <sys/gpio.h> + +#include <sys/audioio.h> +#include <dev/audio_if.h> +#include <dev/auconv.h> + +#include <dev/fdt/fdtvar.h> + +#include <arm/sunxi/sunxi_codec.h> + +#define TX_TRIG_LEVEL 0xf +#define RX_TRIG_LEVEL 0x7 +#define DRQ_CLR_CNT 0x3 + +#define AC_DAC_DPC(_sc) ((_sc)->sc_cfg->DPC) +#define DAC_DPC_EN_DA 0x80000000 +#define AC_DAC_FIFOC(_sc) ((_sc)->sc_cfg->DAC_FIFOC) +#define DAC_FIFOC_FS __BITS(31,29) +#define DAC_FS_48KHZ 0 +#define DAC_FS_32KHZ 1 +#define DAC_FS_24KHZ 2 +#define DAC_FS_16KHZ 3 +#define DAC_FS_12KHZ 4 +#define DAC_FS_8KHZ 5 +#define DAC_FS_192KHZ 6 +#define DAC_FS_96KHZ 7 +#define DAC_FIFOC_FIFO_MODE __BITS(25,24) +#define FIFO_MODE_24_31_8 0 +#define FIFO_MODE_16_31_16 0 +#define FIFO_MODE_16_15_0 1 +#define DAC_FIFOC_DRQ_CLR_CNT __BITS(22,21) +#define DAC_FIFOC_TX_TRIG_LEVEL __BITS(14,8) +#define DAC_FIFOC_MONO_EN __BIT(6) +#define DAC_FIFOC_TX_BITS __BIT(5) +#define DAC_FIFOC_DRQ_EN __BIT(4) +#define DAC_FIFOC_FIFO_FLUSH __BIT(0) +#define AC_DAC_FIFOS(_sc) ((_sc)->sc_cfg->DAC_FIFOS) +#define AC_DAC_TXDATA(_sc) ((_sc)->sc_cfg->DAC_TXDATA) +#define AC_ADC_FIFOC(_sc) ((_sc)->sc_cfg->ADC_FIFOC) +#define ADC_FIFOC_FS __BITS(31,29) +#define ADC_FS_48KHZ 0 +#define ADC_FIFOC_EN_AD __BIT(28) +#define ADC_FIFOC_RX_FIFO_MODE __BIT(24) +#define ADC_FIFOC_RX_TRIG_LEVEL __BITS(12,8) +#define ADC_FIFOC_MONO_EN __BIT(7) +#define ADC_FIFOC_RX_BITS __BIT(6) +#define ADC_FIFOC_DRQ_EN __BIT(4) +#define ADC_FIFOC_FIFO_FLUSH __BIT(0) +#define AC_ADC_FIFOS(_sc) ((_sc)->sc_cfg->ADC_FIFOS) +#define AC_ADC_RXDATA(_sc) ((_sc)->sc_cfg->ADC_RXDATA) +#define AC_DAC_CNT(_sc) ((_sc)->sc_cfg->DAC_CNT) +#define AC_ADC_CNT(_sc) ((_sc)->sc_cfg->ADC_CNT) + +static const struct of_compat_data compat_data[] = { + H3_CODEC_COMPATDATA, + { NULL } +}; + +#define CODEC_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define CODEC_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +static int +sunxi_codec_allocdma(struct sunxi_codec_softc *sc, size_t size, + size_t align, struct sunxi_codec_dma *dma) +{ + int error; + + dma->dma_size = size; + error = bus_dmamem_alloc(sc->sc_dmat, dma->dma_size, align, 0, + dma->dma_segs, 1, &dma->dma_nsegs, BUS_DMA_WAITOK); + if (error) + return error; + + error = bus_dmamem_map(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs, + dma->dma_size, &dma->dma_addr, BUS_DMA_WAITOK | BUS_DMA_COHERENT); + if (error) + goto free; + + error = bus_dmamap_create(sc->sc_dmat, dma->dma_size, dma->dma_nsegs, + dma->dma_size, 0, BUS_DMA_WAITOK, &dma->dma_map); + if (error) + goto unmap; + + error = bus_dmamap_load(sc->sc_dmat, dma->dma_map, dma->dma_addr, + dma->dma_size, NULL, BUS_DMA_WAITOK); + if (error) + goto destroy; + + return 0; + +destroy: + bus_dmamap_destroy(sc->sc_dmat, dma->dma_map); +unmap: + bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size); +free: + bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs); + + return error; +} + +static void +sunxi_codec_freedma(struct sunxi_codec_softc *sc, struct sunxi_codec_dma *dma) +{ + bus_dmamap_unload(sc->sc_dmat, dma->dma_map); + bus_dmamap_destroy(sc->sc_dmat, dma->dma_map); + bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size); + bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs); +} + +static int +sunxi_codec_transfer(struct sunxi_codec_chan *ch) +{ + bus_dma_segment_t seg; + + seg.ds_addr = ch->ch_cur_phys; + seg.ds_len = ch->ch_blksize; + ch->ch_req.dreq_segs = &seg; + ch->ch_req.dreq_nsegs = 1; + + return fdtbus_dma_transfer(ch->ch_dma, &ch->ch_req); +} + +static int +sunxi_codec_open(void *priv, int flags) +{ + return 0; +} + +static void +sunxi_codec_close(void *priv) +{ +} + +static int +sunxi_codec_drain(void *priv) +{ + struct sunxi_codec_softc * const sc = priv; + uint32_t val; + + val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_FIFO_FLUSH); + + val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_FIFO_FLUSH); + + return 0; +} + +static int +sunxi_codec_query_encoding(void *priv, struct audio_encoding *ae) +{ + struct sunxi_codec_softc * const sc = priv; + + return auconv_query_encoding(sc->sc_encodings, ae); +} + +static int +sunxi_codec_set_params(void *priv, int setmode, int usemode, + audio_params_t *play, audio_params_t *rec, + stream_filter_list_t *pfil, stream_filter_list_t *rfil) +{ + struct sunxi_codec_softc * const sc = priv; + int index; + + if (play && (setmode & AUMODE_PLAY)) { + index = auconv_set_converter(&sc->sc_format, 1, + AUMODE_PLAY, play, true, pfil); + if (index < 0) + return EINVAL; + sc->sc_pchan.ch_params = pfil->req_size > 0 ? + pfil->filters[0].param : *play; + } + if (rec && (setmode & AUMODE_RECORD)) { + index = auconv_set_converter(&sc->sc_format, 1, + AUMODE_RECORD, rec, true, rfil); + if (index < 0) + return EINVAL; + sc->sc_rchan.ch_params = rfil->req_size > 0 ? + rfil->filters[0].param : *rec; + } + + return 0; +} + +static int +sunxi_codec_set_port(void *priv, mixer_ctrl_t *mc) +{ + struct sunxi_codec_softc * const sc = priv; + + return sc->sc_cfg->set_port(sc, mc); +} + +static int +sunxi_codec_get_port(void *priv, mixer_ctrl_t *mc) +{ + struct sunxi_codec_softc * const sc = priv; + + return sc->sc_cfg->get_port(sc, mc); +} + +static int +sunxi_codec_query_devinfo(void *priv, mixer_devinfo_t *di) +{ + struct sunxi_codec_softc * const sc = priv; + + return sc->sc_cfg->query_devinfo(sc, di); +} + +static void * +sunxi_codec_allocm(void *priv, int dir, size_t size) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_dma *dma; + int error; + + dma = kmem_alloc(sizeof(*dma), KM_SLEEP); + + error = sunxi_codec_allocdma(sc, size, 16, dma); + if (error) { + kmem_free(dma, sizeof(*dma)); + device_printf(sc->sc_dev, "couldn't allocate DMA memory (%d)\n", + error); + return NULL; + } + + LIST_INSERT_HEAD(&sc->sc_dmalist, dma, dma_list); + + return dma->dma_addr; +} + +static void +sunxi_codec_freem(void *priv, void *addr, size_t size) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_dma *dma; + + LIST_FOREACH(dma, &sc->sc_dmalist, dma_list) + if (dma->dma_addr == addr) { + sunxi_codec_freedma(sc, dma); + LIST_REMOVE(dma, dma_list); + kmem_free(dma, sizeof(*dma)); + break; + } +} + +static paddr_t +sunxi_codec_mappage(void *priv, void *addr, off_t off, int prot) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_dma *dma; + + if (off < 0) + return -1; + + LIST_FOREACH(dma, &sc->sc_dmalist, dma_list) + if (dma->dma_addr == addr) { + return bus_dmamem_mmap(sc->sc_dmat, dma->dma_segs, + dma->dma_nsegs, off, prot, BUS_DMA_WAITOK); + } + + return -1; +} + +static int +sunxi_codec_getdev(void *priv, struct audio_device *adev) +{ + struct sunxi_codec_softc * const sc = priv; + + snprintf(adev->name, sizeof(adev->name), "Allwinner"); + snprintf(adev->version, sizeof(adev->version), "%s", + sc->sc_cfg->name); + snprintf(adev->config, sizeof(adev->config), "sunxicodec"); + + return 0; +} + +static int +sunxi_codec_get_props(void *priv) +{ + return AUDIO_PROP_PLAYBACK|AUDIO_PROP_CAPTURE| + AUDIO_PROP_INDEPENDENT|AUDIO_PROP_MMAP| + AUDIO_PROP_FULLDUPLEX; +} + +static int +sunxi_codec_round_blocksize(void *priv, int bs, int mode, + const audio_params_t *params) +{ + bs &= ~3; + if (bs == 0) + bs = 4; + return bs; +} + +static size_t +sunxi_codec_round_buffersize(void *priv, int dir, size_t bufsize) +{ + return bufsize; +} + +static int +sunxi_codec_trigger_output(void *priv, void *start, void *end, int blksize, + void (*intr)(void *), void *intrarg, const audio_params_t *params) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_chan *ch = &sc->sc_pchan; + struct sunxi_codec_dma *dma; + bus_addr_t pstart; + bus_size_t psize; + uint32_t val; + int error; + + pstart = 0; + psize = (uintptr_t)end - (uintptr_t)start; + + LIST_FOREACH(dma, &sc->sc_dmalist, dma_list) + if (dma->dma_addr == start) { + pstart = dma->dma_map->dm_segs[0].ds_addr; + break; + } + if (pstart == 0) { + device_printf(sc->sc_dev, "bad addr %p\n", start); + return EINVAL; + } + + ch->ch_intr = intr; + ch->ch_intrarg = intrarg; + ch->ch_start_phys = ch->ch_cur_phys = pstart; + ch->ch_end_phys = pstart + psize; + ch->ch_blksize = blksize; + + /* Flush DAC FIFO */ + val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_FIFO_FLUSH); + + /* Clear DAC FIFO status */ + val = CODEC_READ(sc, AC_DAC_FIFOS(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOS(sc), val); + + /* Unmute output */ + if (sc->sc_cfg->mute) + sc->sc_cfg->mute(sc, 0, ch->ch_mode); + + /* Configure DAC FIFO */ + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), + __SHIFTIN(DAC_FS_48KHZ, DAC_FIFOC_FS) | + __SHIFTIN(FIFO_MODE_16_15_0, DAC_FIFOC_FIFO_MODE) | + __SHIFTIN(DRQ_CLR_CNT, DAC_FIFOC_DRQ_CLR_CNT) | + __SHIFTIN(TX_TRIG_LEVEL, DAC_FIFOC_TX_TRIG_LEVEL)); + + /* Enable DAC DRQ */ + val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_DRQ_EN); + + /* Start DMA transfer */ + error = sunxi_codec_transfer(ch); + if (error != 0) { + aprint_error_dev(sc->sc_dev, + "failed to start DMA transfer: %d\n", error); + return error; + } + + return 0; +} + +static int +sunxi_codec_trigger_input(void *priv, void *start, void *end, int blksize, + void (*intr)(void *), void *intrarg, const audio_params_t *params) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_chan *ch = &sc->sc_rchan; + struct sunxi_codec_dma *dma; + bus_addr_t pstart; + bus_size_t psize; + uint32_t val; + int error; + + pstart = 0; + psize = (uintptr_t)end - (uintptr_t)start; + + LIST_FOREACH(dma, &sc->sc_dmalist, dma_list) + if (dma->dma_addr == start) { + pstart = dma->dma_map->dm_segs[0].ds_addr; + break; + } + if (pstart == 0) { + device_printf(sc->sc_dev, "bad addr %p\n", start); + return EINVAL; + } + + ch->ch_intr = intr; + ch->ch_intrarg = intrarg; + ch->ch_start_phys = ch->ch_cur_phys = pstart; + ch->ch_end_phys = pstart + psize; + ch->ch_blksize = blksize; + + /* Flush ADC FIFO */ + val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_FIFO_FLUSH); + + /* Clear ADC FIFO status */ + val = CODEC_READ(sc, AC_ADC_FIFOS(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOS(sc), val); + + /* Unmute input */ + if (sc->sc_cfg->mute) + sc->sc_cfg->mute(sc, 0, ch->ch_mode); + + /* Configure ADC FIFO */ + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), + __SHIFTIN(ADC_FS_48KHZ, ADC_FIFOC_FS) | + __SHIFTIN(RX_TRIG_LEVEL, ADC_FIFOC_RX_TRIG_LEVEL) | + ADC_FIFOC_EN_AD | ADC_FIFOC_RX_FIFO_MODE); + + /* Enable ADC DRQ */ + val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_DRQ_EN); + + /* Start DMA transfer */ + error = sunxi_codec_transfer(ch); + if (error != 0) { + aprint_error_dev(sc->sc_dev, + "failed to start DMA transfer: %d\n", error); + return error; + } + + return 0; +} + +static int +sunxi_codec_halt_output(void *priv) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_chan *ch = &sc->sc_pchan; + uint32_t val; + + /* Disable DMA channel */ + fdtbus_dma_halt(ch->ch_dma); + + /* Mute output */ + if (sc->sc_cfg->mute) + sc->sc_cfg->mute(sc, 1, ch->ch_mode); + + /* Disable DAC DRQ */ + val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val & ~DAC_FIFOC_DRQ_EN); + + ch->ch_intr = NULL; + ch->ch_intrarg = NULL; + + return 0; +} + +static int +sunxi_codec_halt_input(void *priv) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_chan *ch = &sc->sc_rchan; + uint32_t val; + + /* Disable DMA channel */ + fdtbus_dma_halt(ch->ch_dma); + + /* Mute output */ + if (sc->sc_cfg->mute) + sc->sc_cfg->mute(sc, 1, ch->ch_mode); + + /* Disable ADC DRQ */ + val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val & ~ADC_FIFOC_DRQ_EN); + + return 0; +} + +static void +sunxi_codec_get_locks(void *priv, kmutex_t **intr, kmutex_t **thread) +{ + struct sunxi_codec_softc * const sc = priv; + + *intr = &sc->sc_intr_lock; + *thread = &sc->sc_lock; +} + +static const struct audio_hw_if sunxi_codec_hw_if = { + .open = sunxi_codec_open, + .close = sunxi_codec_close, + .drain = sunxi_codec_drain, + .query_encoding = sunxi_codec_query_encoding, + .set_params = sunxi_codec_set_params, + .allocm = sunxi_codec_allocm, + .freem = sunxi_codec_freem, + .mappage = sunxi_codec_mappage, + .getdev = sunxi_codec_getdev, + .set_port = sunxi_codec_set_port, + .get_port = sunxi_codec_get_port, + .query_devinfo = sunxi_codec_query_devinfo, + .get_props = sunxi_codec_get_props, + .round_blocksize = sunxi_codec_round_blocksize, + .round_buffersize = sunxi_codec_round_buffersize, + .trigger_output = sunxi_codec_trigger_output, + .trigger_input = sunxi_codec_trigger_input, + .halt_output = sunxi_codec_halt_output, + .halt_input = sunxi_codec_halt_input, + .get_locks = sunxi_codec_get_locks, +}; + +static void +sunxi_codec_dmaintr(void *priv) +{ + struct sunxi_codec_chan * const ch = priv; + + ch->ch_cur_phys += ch->ch_blksize; + if (ch->ch_cur_phys >= ch->ch_end_phys) + ch->ch_cur_phys = ch->ch_start_phys; + + if (ch->ch_intr) { + ch->ch_intr(ch->ch_intrarg); + sunxi_codec_transfer(ch); + } +} + +static int +sunxi_codec_chan_init(struct sunxi_codec_softc *sc, + struct sunxi_codec_chan *ch, u_int mode, const char *dmaname) +{ + ch->ch_sc = sc; + ch->ch_mode = mode; + ch->ch_dma = fdtbus_dma_get(sc->sc_phandle, dmaname, sunxi_codec_dmaintr, ch); + if (ch->ch_dma == NULL) { + aprint_error(": couldn't get dma channel \"%s\"\n", dmaname); + return ENXIO; + } + + if (mode == AUMODE_PLAY) { + ch->ch_req.dreq_dir = FDT_DMA_WRITE; + ch->ch_req.dreq_dev_phys = + sc->sc_baseaddr + AC_DAC_TXDATA(sc); + } else { + ch->ch_req.dreq_dir = FDT_DMA_READ; + ch->ch_req.dreq_dev_phys = + sc->sc_baseaddr + AC_ADC_RXDATA(sc); + } + ch->ch_req.dreq_mem_opt.opt_bus_width = 16; + ch->ch_req.dreq_mem_opt.opt_burst_len = 4; + ch->ch_req.dreq_dev_opt.opt_bus_width = 16; + ch->ch_req.dreq_dev_opt.opt_burst_len = 4; + + return 0; +} + +static int +sunxi_codec_clock_init(int phandle) +{ + struct fdtbus_reset *rst; + struct clk *clk; + int error; + + /* Set codec clock to 24.576MHz, suitable for 48 kHz sampling rates */ + clk = fdtbus_clock_get(phandle, "codec"); + if (clk == NULL) { + aprint_error(": couldn't find codec clock\n"); + return ENXIO; + } + error = clk_set_rate(clk, 24576000); + if (error != 0) { + aprint_error(": couldn't set codec clock rate: %d\n", error); + return error; + } + error = clk_enable(clk); + if (error != 0) { + aprint_error(": couldn't enable codec clock: %d\n", error); + return error; + } + + /* Enable APB clock */ + clk = fdtbus_clock_get(phandle, "apb"); + if (clk == NULL) { + aprint_error(": couldn't find apb clock\n"); + return ENXIO; + } + error = clk_enable(clk); + if (error != 0) { + aprint_error(": couldn't enable apb clock: %d\n", error); + return error; + } + + /* De-assert reset */ + rst = fdtbus_reset_get_index(phandle, 0); + if (rst == NULL) { + aprint_error(": couldn't find reset\n"); + return ENXIO; + } + error = fdtbus_reset_deassert(rst); + if (error != 0) { + aprint_error(": couldn't de-assert reset: %d\n", error); + return error; + } + + return 0; +} + +static int +sunxi_codec_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compat_data(faa->faa_phandle, compat_data); +} + +static void +sunxi_codec_attach(device_t parent, device_t self, void *aux) +{ + struct sunxi_codec_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + bus_addr_t addr; + bus_size_t size; + uint32_t val; + int error; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + if (sunxi_codec_clock_init(phandle) != 0) + return; + + sc->sc_dev = self; + sc->sc_phandle = phandle; + sc->sc_baseaddr = addr; + sc->sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + sc->sc_dmat = faa->faa_dmat; + LIST_INIT(&sc->sc_dmalist); + sc->sc_cfg = (void *)of_search_compatible(phandle, compat_data)->data; + mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); + mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED); + + if (sunxi_codec_chan_init(sc, &sc->sc_pchan, AUMODE_PLAY, "tx") != 0 || + sunxi_codec_chan_init(sc, &sc->sc_rchan, AUMODE_RECORD, "rx") != 0) { + aprint_error(": couldn't setup channels\n"); + return; + } + + /* Optional PA mute GPIO */ + sc->sc_pin_pa = fdtbus_gpio_acquire(phandle, "allwinner,pa-gpios", GPIO_PIN_OUTPUT); + if (sc->sc_pin_pa != NULL) + fdtbus_gpio_write(sc->sc_pin_pa, 1); + + aprint_naive("\n"); + aprint_normal(": %s\n", sc->sc_cfg->name); + + /* Enable DAC */ + val = CODEC_READ(sc, AC_DAC_DPC(sc)); + val |= DAC_DPC_EN_DA; + CODEC_WRITE(sc, AC_DAC_DPC(sc), val); + + /* Initialize codec */ + if (sc->sc_cfg->init(sc) != 0) { + aprint_error_dev(self, "couldn't initialize codec\n"); + return; + } + + sc->sc_format.mode = AUMODE_PLAY|AUMODE_RECORD; + sc->sc_format.encoding = AUDIO_ENCODING_SLINEAR_LE; + sc->sc_format.validbits = 16; + sc->sc_format.precision = 16; + sc->sc_format.channels = 2; + sc->sc_format.channel_mask = AUFMT_STEREO; + sc->sc_format.frequency_type = 0; + sc->sc_format.frequency[0] = sc->sc_format.frequency[1] = 48000; + + error = auconv_create_encodings(&sc->sc_format, 1, &sc->sc_encodings); + if (error) { + aprint_error_dev(self, "couldn't create encodings\n"); + return; + } + + audio_attach_mi(&sunxi_codec_hw_if, sc, self); +} + +CFATTACH_DECL_NEW(sunxi_codec, sizeof(struct sunxi_codec_softc), + sunxi_codec_match, sunxi_codec_attach, NULL, NULL); + +#ifdef DDB +void sunxicodec_dump(void); + +void +sunxicodec_dump(void) +{ + struct sunxi_codec_softc *sc; + device_t dev; + + dev = device_find_by_driver_unit("sunxicodec", 0); + if (dev == NULL) + return; + sc = device_private(dev); + + device_printf(dev, "AC_DAC_DPC: %08x\n", CODEC_READ(sc, AC_DAC_DPC(sc))); + device_printf(dev, "AC_DAC_FIFOC: %08x\n", CODEC_READ(sc, AC_DAC_FIFOC(sc))); + device_printf(dev, "AC_DAC_FIFOS: %08x\n", CODEC_READ(sc, AC_DAC_FIFOS(sc))); + device_printf(dev, "AC_ADC_FIFOC: %08x\n", CODEC_READ(sc, AC_ADC_FIFOC(sc))); + device_printf(dev, "AC_ADC_FIFOS: %08x\n", CODEC_READ(sc, AC_ADC_FIFOS(sc))); + device_printf(dev, "AC_DAC_CNT: %08x\n", CODEC_READ(sc, AC_DAC_CNT(sc))); + device_printf(dev, "AC_ADC_CNT: %08x\n", CODEC_READ(sc, AC_ADC_CNT(sc))); +} +#endif Index: src/sys/arch/arm/sunxi/sunxi_codec.h diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_codec.h:1.1.2.2 --- /dev/null Wed Aug 9 05:49:50 2017 +++ src/sys/arch/arm/sunxi/sunxi_codec.h Wed Aug 9 05:49:50 2017 @@ -0,0 +1,129 @@ +/* $NetBSD: sunxi_codec.h,v 1.1.2.2 2017/08/09 05:49:50 snj Exp $ */ + +/*- + * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _ARM_SUNXI_CODEC_H +#define _ARM_SUNXI_CODEC_H + +#include <sys/audioio.h> +#include <dev/audio_if.h> +#include <dev/auconv.h> + +#include <dev/fdt/fdtvar.h> + +#include "h3_codec.h" + +struct sunxi_codec_softc; + +struct sunxi_codec_conf { + const char *name; + + /* initialize codec */ + int (*init)(struct sunxi_codec_softc *); + /* toggle DAC/ADC mute */ + void (*mute)(struct sunxi_codec_softc *, int, u_int); + /* mixer controls */ + int (*set_port)(struct sunxi_codec_softc *, + mixer_ctrl_t *); + int (*get_port)(struct sunxi_codec_softc *, + mixer_ctrl_t *); + int (*query_devinfo)(struct sunxi_codec_softc *, + mixer_devinfo_t *); + + /* register map */ + bus_size_t DPC, + DAC_FIFOC, + DAC_FIFOS, + DAC_TXDATA, + ADC_FIFOC, + ADC_FIFOS, + ADC_RXDATA, + DAC_CNT, + ADC_CNT; +}; + +struct sunxi_codec_chan { + struct sunxi_codec_softc *ch_sc; + u_int ch_mode; + + struct fdtbus_dma *ch_dma; + struct fdtbus_dma_req ch_req; + + audio_params_t ch_params; + + bus_addr_t ch_start_phys; + bus_addr_t ch_end_phys; + bus_addr_t ch_cur_phys; + int ch_blksize; + + void (*ch_intr)(void *); + void *ch_intrarg; +}; + +struct sunxi_codec_dma { + LIST_ENTRY(sunxi_codec_dma) dma_list; + bus_dmamap_t dma_map; + void *dma_addr; + size_t dma_size; + bus_dma_segment_t dma_segs[1]; + int dma_nsegs; +}; + +struct sunxi_codec_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + bus_dma_tag_t sc_dmat; + int sc_phandle; + bus_addr_t sc_baseaddr; + + struct sunxi_codec_conf *sc_cfg; + void *sc_codec_priv; + + struct fdtbus_gpio_pin *sc_pin_pa; + + LIST_HEAD(, sunxi_codec_dma) sc_dmalist; + + kmutex_t sc_lock; + kmutex_t sc_intr_lock; + + struct audio_format sc_format; + struct audio_encoding_set *sc_encodings; + + struct sunxi_codec_chan sc_pchan; + struct sunxi_codec_chan sc_rchan; +}; + +#if NH3_CODEC > 0 +extern const struct sunxi_codec_conf sun8i_h3_codecconf; +#define H3_CODEC_COMPATDATA \ + { "allwinner,sun8i-h3-codec", (uintptr_t)&sun8i_h3_codecconf } +#else +#define H3_CODEC_COMPATDATA +#endif + +#endif /* !_ARM_SUNXI_CODEC_H */