/* * mc97.c * * MC97 controller * * Copyright (C) 2021 Sylvain Munaut * SPDX-License-Identifier: LGPL-3.0-or-later */ #include #include #include "console.h" #include "mc97.h" #include "mc97_country.h" #include "config.h" struct wb_mc97 { uint32_t csr; uint32_t lls; uint32_t cra; uint32_t _rsvd; uint32_t gpio_in; uint32_t gpio_out; uint32_t fifo_data; uint32_t fifo_csr; } __attribute__((packed,aligned(4))); #define MC97_CSR_RFI (1 << 3) #define MC97_CSR_GPIO_ENA (1 << 2) #define MC97_CSR_RESET_N (1 << 1) #define MC97_CSR_RUN (1 << 0) #define MC97_LLS_CODEC_READY (1 << 31) #define MC97_LLS_SLOT_REQ(n) (1 << ((n)+16)) #define MC97_LLS_SLOT_VALID(n) (1 << ((n)+16)) #define MC97_CRA_BUSY (1 << 31) #define MC97_CRA_WRITE (1 << 30) #define MC97_CRA_READ_ERR (1 << 29) #define MC97_CRA_ADDR(x) (((x) >> 1) << 16) #define MC97_CRA_VAL(x) (x) #define MC97_CRA_GET_VAL(x) ((x) & 0xffff) #define MC97_FIFO_DATA_EMPTY (1 << 31) #define MC97_FIFO_CSR_PCM_IN_ENABLE (1 << 31) #define MC97_FIFO_CSR_PCM_IN_FLUSH (1 << 30) #define MC97_FIFO_CSR_PCM_IN_FULL (1 << 29) #define MC97_FIFO_CSR_PCM_IN_EMPTY (1 << 28) #define MC97_FIFO_CSR_PCM_IN_LEVEL(x) (((x) >> 16) & 0xfff) #define MC97_FIFO_CSR_PCM_OUT_ENABLE (1 << 15) #define MC97_FIFO_CSR_PCM_OUT_FLUSH (1 << 14) #define MC97_FIFO_CSR_PCM_OUT_FULL (1 << 13) #define MC97_FIFO_CSR_PCM_OUT_EMPTY (1 << 12) #define MC97_FIFO_CSR_PCM_OUT_LEVEL(x) ((x) & 0xfff) static volatile struct wb_mc97 * const mc97_regs = (void*)(MC97_BASE); static struct { uint16_t rc_46; /* Cache of reg 0x46 */ uint16_t rc_5c; /* Cache of reg 0x5c */ uint16_t rc_62; /* Cache of reg 0x62 */ bool test_ring; } g_mc97; void mc97_codec_reg_write(uint8_t addr, uint16_t val) { /* Submit request */ mc97_regs->cra = MC97_CRA_WRITE | MC97_CRA_ADDR(addr) | MC97_CRA_VAL(val); /* Wait until not busy */ while (mc97_regs->cra & MC97_CRA_BUSY); } uint16_t mc97_codec_reg_read(uint8_t addr) { uint32_t v; /* Submit request */ mc97_regs->cra = MC97_CRA_ADDR(addr); /* Wait until not busy */ while ((v = mc97_regs->cra) & MC97_CRA_BUSY); /* Check for read errors */ if (v & MC97_CRA_READ_ERR) return 0xffff; /* Not much we can do */ /* Return result */ return MC97_CRA_GET_VAL(v); } void mc97_init(void) { /* Initialize controller and reset codec */ mc97_regs->csr = MC97_CSR_RUN; mc97_regs->csr = MC97_CSR_RUN | MC97_CSR_RESET_N | MC97_CSR_GPIO_ENA; /* Init the codec */ mc97_codec_reg_write(0x40, 0x1f40); /* Line 1 rate 8 kHz */ mc97_codec_reg_write(0x3e, 0xf000); /* Power up */ mc97_codec_reg_write(0x46, 0x0000); /* Mute Off, no gain/attenuation */ mc97_codec_reg_write(0x4c, 0x002a); /* GPIO Direction */ mc97_codec_reg_write(0x4e, 0x002a); /* GPIO polarity/type */ /* Init cache */ g_mc97.rc_46 = 0x0000; g_mc97.rc_5c = 0x0000; g_mc97.rc_62 = 0x0000; /* Country default */ mc97_select_country(0); } void mc97_debug(void) { printf("CSR : %08x\n", mc97_regs->csr); printf("LLS : %08x\n", mc97_regs->lls); printf("CRA : %08x\n", mc97_regs->cra); printf("GI : %08x\n", mc97_regs->gpio_in); printf("GO : %08x\n", mc97_regs->gpio_out); printf("Fdat : %08x\n", mc97_regs->fifo_data); printf("Fcsr : %08x\n", mc97_regs->fifo_csr); } bool mc97_select_country(int cc) { for (int i=0; country_data[i].cc >= 0; i++) { /* Match ? */ if (country_data[i].cc != cc) continue; #if 0 printf("Configured for %s\n", country_data[i].name); #endif /* Configure */ g_mc97.rc_5c = (g_mc97.rc_5c & 0xff02) | country_data[i].regs[0]; g_mc97.rc_62 = (g_mc97.rc_62 & 0xff87) | country_data[i].regs[1]; mc97_codec_reg_write(0x5c, g_mc97.rc_5c); mc97_codec_reg_write(0x62, g_mc97.rc_62); /* Done */ return true; } return false; } void mc97_set_aux_relay(bool disconnect) { mc97_regs->gpio_out = (mc97_regs->gpio_out & ~(1 << 8)) | (disconnect << 8); } void mc97_set_hook(enum mc97_hook_state s) { uint32_t gpio_out = mc97_regs->gpio_out & ~((1 << 4) | (1 << 6)); switch (s) { case ON_HOOK: break; case CALLER_ID: gpio_out |= (1 << 6); break; case OFF_HOOK: gpio_out |= (1 << 4); break; } mc97_regs->gpio_out = gpio_out; } void mc97_test_ring(void) { g_mc97.test_ring = true; } bool mc97_get_ring_detect(void) { if (g_mc97.test_ring) { g_mc97.test_ring = false; return true; } return ( (mc97_regs->gpio_in & (1 << 5)) && (mc97_regs->csr & MC97_CSR_RFI) ) ? true : false; } void mc97_set_loopback(enum mc97_loopback_mode m) { mc97_codec_reg_write(0x56, m); } uint8_t mc97_get_rx_gain(void) { return (g_mc97.rc_46 & 0xf) * 3; } void mc97_set_rx_gain(uint8_t gain) { gain = (gain > 45) ? 0xf : (gain / 3); g_mc97.rc_46 = (g_mc97.rc_46 & 0xff80) | (gain << 8); mc97_codec_reg_write(0x46, g_mc97.rc_46); } bool mc97_get_rx_mute(void) { return (g_mc97.rc_46 & 0x0080) ? true : false; } void mc97_set_rx_mute(bool mute) { g_mc97.rc_46 = (g_mc97.rc_46 & 0xff7f) | (mute ? 0x0080 : 0x0000); mc97_codec_reg_write(0x46, g_mc97.rc_46); } uint8_t mc97_get_tx_attenuation(void) { return ((g_mc97.rc_46 >> 8) & 0xf) * 3; } void mc97_set_tx_attenuation(uint8_t attenuation) { attenuation = (attenuation > 45) ? 0xf : (attenuation / 3); g_mc97.rc_46 = (g_mc97.rc_46 & 0x80ff) | (attenuation << 8); mc97_codec_reg_write(0x46, g_mc97.rc_46); } bool mc97_get_tx_mute(void) { return (g_mc97.rc_46 & 0x8000) ? true : false; } void mc97_set_tx_mute(bool mute) { g_mc97.rc_46 = (g_mc97.rc_46 & 0x7fff) | (mute ? 0x8000 : 0x0000); mc97_codec_reg_write(0x46, g_mc97.rc_46); } void mc97_flow_rx_reset(void) { mc97_regs->fifo_csr = (mc97_regs->fifo_csr & ~MC97_FIFO_CSR_PCM_IN_ENABLE) | MC97_FIFO_CSR_PCM_IN_FLUSH; while (mc97_regs->fifo_csr & MC97_FIFO_CSR_PCM_IN_FLUSH); } void mc97_flow_rx_start(void) { mc97_regs->fifo_csr |= MC97_FIFO_CSR_PCM_IN_ENABLE; } void mc97_flow_rx_stop(void) { mc97_regs->fifo_csr &= ~MC97_FIFO_CSR_PCM_IN_ENABLE; } int mc97_flow_rx_pull(int16_t *data, int n) { for (int i=0; ififo_data; if (v & MC97_FIFO_DATA_EMPTY) return i; data[i] = v & 0xffff; } return n; } int mc97_flow_rx_level(void) { return MC97_FIFO_CSR_PCM_IN_LEVEL(mc97_regs->fifo_csr); } bool mc97_flow_rx_active(void) { return mc97_regs->fifo_csr & MC97_FIFO_CSR_PCM_IN_ENABLE; } void mc97_flow_tx_reset(void) { mc97_regs->fifo_csr = (mc97_regs->fifo_csr & ~MC97_FIFO_CSR_PCM_OUT_ENABLE) | MC97_FIFO_CSR_PCM_OUT_FLUSH; while (mc97_regs->fifo_csr & MC97_FIFO_CSR_PCM_OUT_FLUSH); } void mc97_flow_tx_start(void) { mc97_regs->fifo_csr |= MC97_FIFO_CSR_PCM_OUT_ENABLE; } void mc97_flow_tx_stop(void) { mc97_regs->fifo_csr &= ~MC97_FIFO_CSR_PCM_OUT_ENABLE; } int mc97_flow_tx_push(int16_t *data, int n) { int max = MC97_FIFO_SIZE - MC97_FIFO_CSR_PCM_OUT_LEVEL(mc97_regs->fifo_csr); if (n > max) n = max; for (int i=0; ififo_data = data[i]; return n; } int mc97_flow_tx_level(void) { return MC97_FIFO_CSR_PCM_OUT_LEVEL(mc97_regs->fifo_csr); } bool mc97_flow_tx_active(void) { return mc97_regs->fifo_csr & MC97_FIFO_CSR_PCM_OUT_ENABLE; }