/* * File : drv_pl041.c * This file is part of RT-Thread RTOS * COPYRIGHT (C) 2017, RT-Thread Development Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Change Logs: * Date Author Notes * 2018-05-25 RT-Thread the first version */ #include #include #include "drv_pl041.h" #include "drv_ac97.h" #include "realview.h" #define DBG_SECTION_NAME "PL041" // #define DBG_LEVEL DBG_LOG // #define DBG_LEVEL DBG_INFO #define DBG_LEVEL DBG_WARNING // #define DBG_LEVEL DBG_ERROR #include #define FRAME_PERIOD_US (50) #define PL041_CHANNLE_NUM (4) #define PL041_READ(_a) (*(volatile rt_uint32_t *)(_a)) #define PL041_WRITE(_a, _v) (*(volatile rt_uint32_t *)(_a) = (_v)) struct pl041_irq_def { pl041_irq_fun_t fun; void *user_data; }; static struct pl041_irq_def irq_tbl[PL041_CHANNLE_NUM]; static void aaci_pl041_delay(rt_uint32_t us) { volatile int i; for (i = us * 10; i != 0; i--); } static void aaci_ac97_select_codec(void) { rt_uint32_t v, maincr; maincr = AACI_MAINCR_SCRA(0) | AACI_MAINCR_IE | AACI_MAINCR_SL1RXEN | \ AACI_MAINCR_SL1TXEN | AACI_MAINCR_SL2RXEN | AACI_MAINCR_SL2TXEN; v = PL041_READ(&PL041->slfr); if (v & AACI_SLFR_2RXV) { PL041_READ(&PL041->sl2rx); } if (v & AACI_SLFR_1RXV) { PL041_READ(&PL041->sl1rx); } if (maincr != PL041_READ(&PL041->maincr)) { PL041_WRITE(&PL041->maincr, maincr); aaci_pl041_delay(1); } } void aaci_ac97_write(rt_uint16_t reg, rt_uint16_t val) { rt_uint32_t v, timeout; aaci_ac97_select_codec(); PL041_WRITE(&PL041->sl2tx, val << 4); PL041_WRITE(&PL041->sl1tx, reg << 12); aaci_pl041_delay(FRAME_PERIOD_US); timeout = FRAME_PERIOD_US * 8; do { aaci_pl041_delay(1); v = PL041_READ(&PL041->slfr); } while ((v & (AACI_SLFR_1TXB | AACI_SLFR_2TXB)) && --timeout); if (v & (AACI_SLFR_1TXB | AACI_SLFR_2TXB)) { LOG_E("timeout waiting for write to complete"); } } rt_uint16_t aaci_ac97_read(rt_uint16_t reg) { rt_uint32_t v, timeout, retries = 10; aaci_ac97_select_codec(); PL041_WRITE(&PL041->sl1tx, (reg << 12) | (1 << 19)); aaci_pl041_delay(FRAME_PERIOD_US); timeout = FRAME_PERIOD_US * 8; do { aaci_pl041_delay(1); v = PL041_READ(&PL041->slfr); } while ((v & AACI_SLFR_1TXB) && --timeout); if (v & AACI_SLFR_1TXB) { LOG_E("timeout on slot 1 TX busy"); v = ~0x0; return v; } aaci_pl041_delay(FRAME_PERIOD_US); timeout = FRAME_PERIOD_US * 8; do { aaci_pl041_delay(1); v = PL041_READ(&PL041->slfr) & (AACI_SLFR_1RXV | AACI_SLFR_2RXV); } while ((v != (AACI_SLFR_1RXV | AACI_SLFR_2RXV)) && --timeout); if (v != (AACI_SLFR_1RXV | AACI_SLFR_2RXV)) { LOG_E("timeout on RX valid"); v = ~0x0; return v; } do { v = PL041_READ(&PL041->sl1rx) >> 12; if (v == reg) { v = PL041_READ(&PL041->sl2rx) >> 4; break; } else if (--retries) { LOG_E("ac97 read back fail. retry"); continue; } else { LOG_E("wrong ac97 register read back (%x != %x)", v, reg); v = ~0x0; } } while (retries); return v; } int aaci_pl041_channle_disable(int channle) { rt_uint32_t v; void *p_rx, *p_tx; p_rx = (void *)((rt_uint32_t)(&PL041->rxcr1) + channle * 0x14); p_tx = (void *)((rt_uint32_t)(&PL041->txcr1) + channle * 0x14); v = PL041_READ(p_rx); v &= ~AACI_CR_EN; PL041_WRITE(p_rx, v); v = PL041_READ(p_tx); v &= ~AACI_CR_EN; PL041_WRITE(p_tx, v); return 0; } int aaci_pl041_channle_enable(int channle) { rt_uint32_t v; void *p_rx, *p_tx; p_rx = (void *)((rt_uint32_t)(&PL041->rxcr1) + channle * 0x14); p_tx = (void *)((rt_uint32_t)(&PL041->txcr1) + channle * 0x14); v = PL041_READ(p_rx); v |= AACI_CR_EN; PL041_WRITE(p_rx, v); v = PL041_READ(p_tx); v |= AACI_CR_EN; PL041_WRITE(p_tx, v); return 0; } int aaci_pl041_channle_read(int channle, rt_uint16_t *buff, int count) { void *p_data, *p_status; int i = 0; p_status = (void *)((rt_uint32_t)(&PL041->sr1) + channle * 0x14); p_data = (void *)((rt_uint32_t)(&(PL041->dr1[0])) + channle * 0x20); for (i = 0; (!(PL041_READ(p_status) & AACI_SR_RXFE)) && (i < count); i++) { buff[i] = (rt_uint16_t)PL041_READ(p_data); } return i; } int aaci_pl041_channle_write(int channle, rt_uint16_t *buff, int count) { void *p_data, *p_status; int i = 0; p_status = (void *)((rt_uint32_t)(&PL041->sr1) + channle * 0x14); p_data = (void *)((rt_uint32_t)(&(PL041->dr1[0])) + channle * 0x20); for (i = 0; (!(PL041_READ(p_status) & AACI_SR_TXFF)) && (i < count); i++) { PL041_WRITE(p_data, buff[i]); } return i; } int aaci_pl041_channle_cfg(int channle, pl041_cfg_t cgf) { rt_uint32_t v; void *p_rx, *p_tx; p_rx = (void *)((rt_uint32_t)(&PL041->rxcr1) + channle * 0x14); p_tx = (void *)((rt_uint32_t)(&PL041->txcr1) + channle * 0x14); v = AACI_CR_FEN | AACI_CR_SZ16 | cgf->itype; PL041_WRITE(p_rx, v); v = AACI_CR_FEN | AACI_CR_SZ16 | cgf->otype; PL041_WRITE(p_tx, v); ac97_set_vol(cgf->vol); ac97_set_rate(cgf->rate); return 0; } void aaci_pl041_irq_enable(int channle, rt_uint32_t vector) { rt_uint32_t v; void *p_irq; vector &= vector & 0x7f; p_irq = (void *)((rt_uint32_t)(&PL041->iie1) + channle * 0x14); v = PL041_READ(p_irq); v |= vector; PL041_WRITE(p_irq, v); } void aaci_pl041_irq_disable(int channle, rt_uint32_t vector) { rt_uint32_t v; void *p_irq; vector &= vector & 0x7f; p_irq = (void *)((rt_uint32_t)(&PL041->iie1) + channle * 0x14); v = PL041_READ(p_irq); v &= ~vector; PL041_WRITE(p_irq, v); } rt_err_t aaci_pl041_irq_register(int channle, pl041_irq_fun_t fun, void *user_data) { if (channle < 0 || channle >= PL041_CHANNLE_NUM) { LOG_E("%s channle:%d err.", __FUNCTION__, channle); return -RT_ERROR; } irq_tbl[channle].fun = fun; irq_tbl[channle].user_data = user_data; return RT_EOK; } rt_err_t aaci_pl041_irq_unregister(int channle) { if (channle < 0 || channle >= PL041_CHANNLE_NUM) { LOG_E("%s channle:%d err.", __FUNCTION__, channle); return -RT_ERROR; } irq_tbl[channle].fun = RT_NULL; irq_tbl[channle].user_data = RT_NULL; return RT_EOK; } static void aaci_pl041_irq_handle(int irqno, void *param) { rt_uint32_t mask, channle, m; struct pl041_irq_def *_irq = param; void *p_status; mask = PL041_READ(&PL041->allints); PL041_WRITE(PL041->intclr, mask); for (channle = 0; (channle < PL041_CHANNLE_NUM) && (mask); channle++) { mask = mask >> 7; m = mask & 0x7f; if (m & AACI_ISR_ORINTR) { LOG_W("RX overrun on chan %d", channle); } if (m & AACI_ISR_RXTOINTR) { LOG_W("RX timeout on chan %d", channle); } if (mask & AACI_ISR_URINTR) { LOG_W("TX underrun on chan %d", channle); } p_status = (void *)((rt_uint32_t)(&PL041->sr1) + channle * 0x14); if (_irq[channle].fun != RT_NULL) { _irq[channle].fun(PL041_READ(p_status), _irq[channle].user_data); } } } rt_err_t aaci_pl041_init(void) { rt_uint32_t i, maincr; maincr = AACI_MAINCR_SCRA(0) | AACI_MAINCR_IE | AACI_MAINCR_SL1RXEN | \ AACI_MAINCR_SL1TXEN | AACI_MAINCR_SL2RXEN | AACI_MAINCR_SL2TXEN; for (i = 0; i < 4; i++) { void *base = (void *)((rt_uint32_t)(&PL041->rxcr1) + i * 0x14); PL041_WRITE(base + AACI_IE, 0); PL041_WRITE(base + AACI_TXCR, 0); PL041_WRITE(base + AACI_RXCR, 0); } PL041_WRITE(&PL041->intclr, 0x1fff); PL041_WRITE(&PL041->maincr, maincr); PL041_WRITE(&PL041->reset, 0); aaci_pl041_delay(2); PL041_WRITE(&PL041->reset, RESET_NRST); rt_hw_interrupt_install(43, aaci_pl041_irq_handle, &irq_tbl, "aaci_pl041"); rt_hw_interrupt_umask(43); return 0; } #if 0 #define PL041_DUMP(_v) rt_kprintf("%32s:addr:0x%08x data:0x%08x\n", #_v, &(_v), (_v)) int _aaci_pl041_reg_dump(int argc, char **argv) { PL041_DUMP(PL041->rxcr1); PL041_DUMP(PL041->txcr1); PL041_DUMP(PL041->sr1); PL041_DUMP(PL041->isr1); PL041_DUMP(PL041->iie1); PL041_DUMP(PL041->rxcr2); PL041_DUMP(PL041->txcr2); PL041_DUMP(PL041->sr2); PL041_DUMP(PL041->isr2); PL041_DUMP(PL041->iie2); PL041_DUMP(PL041->rxcr3); PL041_DUMP(PL041->txcr3); PL041_DUMP(PL041->sr3); PL041_DUMP(PL041->isr3); PL041_DUMP(PL041->iie3); PL041_DUMP(PL041->rxcr4); PL041_DUMP(PL041->txcr4); PL041_DUMP(PL041->sr4); PL041_DUMP(PL041->isr4); PL041_DUMP(PL041->iie4); PL041_DUMP(PL041->sl1rx); PL041_DUMP(PL041->sl1tx); PL041_DUMP(PL041->sl2rx); PL041_DUMP(PL041->sl2tx); PL041_DUMP(PL041->sl12rx); PL041_DUMP(PL041->sl12tx); PL041_DUMP(PL041->slfr); PL041_DUMP(PL041->slistat); PL041_DUMP(PL041->slien); PL041_DUMP(PL041->intclr); PL041_DUMP(PL041->maincr); PL041_DUMP(PL041->reset); PL041_DUMP(PL041->sync); PL041_DUMP(PL041->allints); PL041_DUMP(PL041->mainfr); PL041_DUMP(PL041->dr1[0]); PL041_DUMP(PL041->dr2[0]); PL041_DUMP(PL041->dr3[0]); PL041_DUMP(PL041->dr4[0]); return 0; } FINSH_FUNCTION_EXPORT_ALIAS(_aaci_pl041_reg_dump, __cmd_pl041_dump, aaci pl041 dump reg.); #endif