/* Calypso DBB internal TPU (Time Processing Unit) Driver */ /* (C) 2010 by Harald Welte * * All Rights Reserved * * 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. * */ #include #include #include #include #include #include #include /* Using TPU_DEBUG you will send special HLDC messages to the host PC * containing the full TPU RAM content at the time you call tpu_enable() */ //#define TPU_DEBUG #define BASE_ADDR_TPU 0xffff1000 #define TPU_REG(x) (BASE_ADDR_TPU+(x)) #define BASE_ADDR_TPU_RAM 0xffff9000 #define TPU_RAM_END 0xffff97ff enum tpu_reg_arm { TPU_CTRL = 0x0, /* Control & Status Register */ INT_CTRL = 0x2, /* Interrupt Control Register */ INT_STAT = 0x4, /* Interrupt Status Register */ TPU_OFFSET = 0xC, /* Offset operand value register */ TPU_SYNCHRO = 0xE, /* synchro operand value register */ IT_DSP_PG = 0x20, }; enum tpu_ctrl_bits { TPU_CTRL_RESET = (1 << 0), TPU_CTRL_PAGE = (1 << 1), TPU_CTRL_EN = (1 << 2), /* unused */ TPU_CTRL_DSP_EN = (1 << 4), /* unused */ TPU_CTRL_MCU_RAM_ACC = (1 << 6), TPU_CTRL_TSP_RESET = (1 << 7), TPU_CTRL_IDLE = (1 << 8), TPU_CTRL_WAIT = (1 << 9), TPU_CTRL_CK_ENABLE = (1 << 10), TPU_CTRL_FULL_WRITE = (1 << 11), }; enum tpu_int_ctrl_bits { ICTRL_MCU_FRAME = (1 << 0), ICTRL_MCU_PAGE = (1 << 1), ICTRL_DSP_FRAME = (1 << 2), ICTRL_DSP_FRAME_FORCE = (1 << 3), }; static uint16_t *tpu_ptr = (uint16_t *)BASE_ADDR_TPU_RAM; #ifdef TPU_DEBUG #include #include static struct msgb *tpu_debug_msg = NULL; static void tpu_debug_alloc(void) { tpu_debug_msg = sercomm_alloc_msgb(sizeof(uint32_t) + 64*2); if (!tpu_debug_msg) printf("UNABLE TO ALLOC TPU DBG\n"); } static void tpu_debug_flush(void) { if (tpu_debug_msg) { sercomm_sendmsg(SC_DLCI_DEBUG, tpu_debug_msg); tpu_debug_msg = NULL; } tpu_debug_alloc(); } static void tpu_debug_enqueue(uint16_t instr) { uint16_t *u16_out; if (!tpu_debug_msg) return; if (tpu_ptr == (uint16_t *) BASE_ADDR_TPU_RAM) { /* prepend tpu memory dump with frame number */ uint32_t *fn = (uint32_t *) msgb_put(tpu_debug_msg, sizeof(fn)); *fn = l1s.current_time.fn; } if (msgb_tailroom(tpu_debug_msg) >= sizeof(instr)) { /* cannot use msgb_put_u16 as host program expects little endian */ u16_out = (uint16_t *) msgb_put(tpu_debug_msg, sizeof(instr)); *u16_out = instr; } } #else static void tpu_debug_alloc(void) { } static void tpu_debug_flush(void) { } static void tpu_debug_enqueue(uint16_t instr) { } #endif #define BIT_SET 1 #define BIT_CLEAR 0 /* wait for a certain control bit to be set */ static int tpu_wait_ctrl_bit(uint16_t bit, int set) { int timeout = 10*1000; while (1) { uint16_t reg = readw(TPU_REG(TPU_CTRL)); if (set) { if (reg & bit) break; } else { if (!(reg & bit)) break; } timeout--; if (timeout <= 0) { puts("Timeout while waiting for TPU ctrl bit!\n"); return -1; } } return 0; } /* assert or de-assert TPU reset */ void tpu_reset(int active) { uint16_t reg; printd("tpu_reset(%u)\n", active); reg = readw(TPU_REG(TPU_CTRL)); if (active) { reg |= (TPU_CTRL_RESET|TPU_CTRL_TSP_RESET); writew(reg, TPU_REG(TPU_CTRL)); tpu_wait_ctrl_bit(TPU_CTRL_RESET, BIT_SET); } else { reg &= ~(TPU_CTRL_RESET|TPU_CTRL_TSP_RESET); writew(reg, TPU_REG(TPU_CTRL)); tpu_wait_ctrl_bit(TPU_CTRL_RESET, BIT_CLEAR); } } /* Enable or Disable a new scenario loaded into the TPU */ void tpu_enable(int active) { uint16_t reg = readw(TPU_REG(TPU_CTRL)); printd("tpu_enable(%u)\n", active); if (active) reg |= TPU_CTRL_EN; else reg &= ~TPU_CTRL_EN; writew(reg, TPU_REG(TPU_CTRL)); tpu_debug_flush(); /* After the new scenario is loaded, TPU switches the MCU-visible memory * page, i.e. we can write without any danger */ tpu_rewind(); #if 0 { int i; uint16_t oldreg = 0; for (i = 0; i < 100000; i++) { reg = readw(TPU_REG(TPU_CTRL)); if (i == 0 || oldreg != reg) { printd("%d TPU state: 0x%04x\n", i, reg); } oldreg = reg; } } #endif } /* Enable or Disable the clock of the TPU Module */ void tpu_clk_enable(int active) { uint16_t reg = readw(TPU_REG(TPU_CTRL)); printd("tpu_clk_enable(%u)\n", active); if (active) { reg |= TPU_CTRL_CK_ENABLE; writew(reg, TPU_REG(TPU_CTRL)); tpu_wait_ctrl_bit(TPU_CTRL_CK_ENABLE, BIT_SET); } else { reg &= ~TPU_CTRL_CK_ENABLE; writew(reg, TPU_REG(TPU_CTRL)); tpu_wait_ctrl_bit(TPU_CTRL_CK_ENABLE, BIT_CLEAR); } } /* Enable Frame Interrupt generation on next frame. DSP will reset it */ void tpu_dsp_frameirq_enable(void) { uint16_t reg = readw(TPU_REG(TPU_CTRL)); reg |= TPU_CTRL_DSP_EN; writew(reg, TPU_REG(TPU_CTRL)); tpu_wait_ctrl_bit(TPU_CTRL_DSP_EN, BIT_SET); } /* Is a Frame interrupt still pending for the DSP ? */ int tpu_dsp_fameirq_pending(void) { uint16_t reg = readw(TPU_REG(TPU_CTRL)); if (reg & TPU_CTRL_DSP_EN) return 1; return 0; } void tpu_rewind(void) { dputs("tpu_rewind()\n"); tpu_ptr = (uint16_t *) BASE_ADDR_TPU_RAM; } void tpu_enqueue(uint16_t instr) { printd("tpu_enqueue(tpu_ptr=%p, instr=0x%04x)\n", tpu_ptr, instr); tpu_debug_enqueue(instr); *tpu_ptr++ = instr; if (tpu_ptr > (uint16_t *) TPU_RAM_END) puts("TPU enqueue beyond end of TPU memory\n"); } void tpu_init(void) { uint16_t *ptr; /* Put TPU into Reset and enable clock */ tpu_reset(1); tpu_clk_enable(1); /* set all TPU RAM to zero */ for (ptr = (uint16_t *) BASE_ADDR_TPU_RAM; ptr < (uint16_t *) TPU_RAM_END; ptr++) *ptr = 0x0000; /* Get TPU out of reset */ tpu_reset(0); /* Disable all interrupts */ writeb(0x7, TPU_REG(INT_CTRL)); tpu_rewind(); tpu_enq_offset(0); tpu_enq_sync(0); } void tpu_test(void) { int i; /* program a sequence of TSPACT events into the TPU */ for (i = 0; i < 10; i++) { puts("TSP ACT enable: "); tsp_act_enable(0x0001); tpu_enq_wait(10); puts("TSP ACT disable: "); tsp_act_disable(0x0001); tpu_enq_wait(10); } tpu_enq_sleep(); /* tell the chip to execute the scenario */ tpu_enable(1); } void tpu_wait_idle(void) { dputs("Waiting for TPU Idle "); /* Wait until TPU is doing something */ delay_us(3); /* Wait until TPU is idle */ while (readw(TPU_REG(TPU_CTRL)) & TPU_CTRL_IDLE) dputchar('.'); dputs("Done!\n"); } void tpu_frame_irq_en(int mcu, int dsp) { uint8_t reg = readb(TPU_REG(INT_CTRL)); if (mcu) reg &= ~ICTRL_MCU_FRAME; else reg |= ICTRL_MCU_FRAME; if (dsp) reg &= ~ICTRL_DSP_FRAME; else reg |= ICTRL_DSP_FRAME; writeb(reg, TPU_REG(INT_CTRL)); } void tpu_force_dsp_frame_irq(void) { uint8_t reg = readb(TPU_REG(INT_CTRL)); reg |= ICTRL_DSP_FRAME_FORCE; writeb(reg, TPU_REG(INT_CTRL)); } uint16_t tpu_get_offset(void) { return readw(TPU_REG(TPU_OFFSET)); } uint16_t tpu_get_synchro(void) { return readw(TPU_REG(TPU_SYNCHRO)); } /* add two numbers, modulo 5000, and ensure the result is positive */ uint16_t add_mod5000(int16_t a, int16_t b) { int32_t sum = (int32_t)a + (int32_t)b; sum %= 5000; /* wrap around zero */ if (sum < 0) sum += 5000; return sum; }