/* Driver for Analog Baseband Circuit (TWL3025) */ /* (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. * * 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. * */ #include #include #include #include #include #include #include #include #include #include /* TWL3025 */ #define REG_PAGE(n) (n >> 7) #define REG_ADDR(n) (n & 0x3f) #define TWL3025_DEV_IDX 0 /* On the SPI bus */ #define TWL3025_TSP_DEV_IDX 0 /* On the TSP bus */ struct twl3025 { uint8_t page; }; static struct twl3025 twl3025_state; /* Switch the register page of the TWL3025 */ static void twl3025_switch_page(uint8_t page) { if (page == 0) twl3025_reg_write(PAGEREG, 1 << 0); else twl3025_reg_write(PAGEREG, 1 << 1); twl3025_state.page = page; } static void handle_charger(void) { uint16_t status; printd("handle_charger();"); status = twl3025_reg_read(VRPCSTS); // printd("\nvrpcsts: 0x%02x", status); if (status & 0x40) { printd(" inserted\n"); } else { printd(" removed\n"); } // twl3025_dump_madc(); } static void handle_adc_done(void) { printd("handle_adc_done();"); } static void twl3025_irq(enum irq_nr nr) { uint16_t src; printd("twl3025_irq: 0x%02x\n",nr); switch (nr){ case IRQ_EXTERNAL: // charger in/out, pwrbtn, adc done src = twl3025_reg_read(ITSTATREG); // printd("itstatreg 0x%02x\n", src); if (src & 0x08) handle_charger(); if (src & 0x20) handle_adc_done(); break; case IRQ_EXTERNAL_FIQ: // vcc <2.8V emergency power off puts("\nBROWNOUT!1!"); twl3025_power_off(); break; default: return; } } void twl3025_init(void) { spi_init(); twl3025_switch_page(0); twl3025_clk13m(1); twl3025_reg_write(AFCCTLADD, 0x01); /* AFCCK(1:0) must not be zero! */ twl3025_unit_enable(TWL3025_UNIT_AFC, 1); irq_register_handler(IRQ_EXTERNAL, &twl3025_irq); irq_config(IRQ_EXTERNAL, 0, 0, 0); irq_enable(IRQ_EXTERNAL); irq_register_handler(IRQ_EXTERNAL_FIQ, &twl3025_irq); irq_config(IRQ_EXTERNAL_FIQ, 1, 0, 0); irq_enable(IRQ_EXTERNAL_FIQ); } void twl3025_reg_write(uint8_t reg, uint16_t data) { uint16_t tx; printd("tw3025_reg_write(%u,%u)=0x%04x\n", REG_PAGE(reg), REG_ADDR(reg), data); if (reg != PAGEREG && REG_PAGE(reg) != twl3025_state.page) twl3025_switch_page(REG_PAGE(reg)); tx = ((data & 0x3ff) << 6) | (REG_ADDR(reg) << 1); spi_xfer(TWL3025_DEV_IDX, 16, &tx, NULL); } void twl3025_tsp_write(uint8_t data) { tsp_write(TWL3025_TSP_DEV_IDX, 7, data); } uint16_t twl3025_reg_read(uint8_t reg) { uint16_t tx, rx; if (REG_PAGE(reg) != twl3025_state.page) twl3025_switch_page(REG_PAGE(reg)); tx = (REG_ADDR(reg) << 1) | 1; /* A read cycle contains two SPI transfers */ spi_xfer(TWL3025_DEV_IDX, 16, &tx, &rx); delay_ms(1); spi_xfer(TWL3025_DEV_IDX, 16, &tx, &rx); rx >>= 6; printd("tw3025_reg_read(%u,%u)=0x%04x\n", REG_PAGE(reg), REG_ADDR(reg), rx); return rx; } static void twl3025_wait_ibic_access(void) { /* Wait 6 * 32kHz clock cycles for first IBIC access (187us + 10% = 210us) */ delay_ms(1); } void twl3025_power_off(void) { twl3025_reg_write(VRPCDEV, 0x01); } void twl3025_clk13m(int enable) { if (enable) { twl3025_reg_write(TOGBR2, TOGBR2_ACTS); twl3025_wait_ibic_access(); /* for whatever reason we need to do this twice */ twl3025_reg_write(TOGBR2, TOGBR2_ACTS); twl3025_wait_ibic_access(); } else { twl3025_reg_write(TOGBR2, TOGBR2_ACTR); twl3025_wait_ibic_access(); } } #define TSP_DELAY 6 /* 13* Tclk6M5 = ~ 3 GSM Qbits + 3 TPU instructions */ #define BDLON_TO_BDLCAL 6 #define BDLCAL_DURATION 66 #define BDLON_TO_BDLENA 7 #define BULON_TO_BULENA 16 /* Enqueue a series of TSP commands in the TPU to (de)activate the downlink path */ void twl3025_downlink(int on, int16_t at) { int16_t bdl_ena = at - TSP_DELAY - 6; if (on) { if (bdl_ena < 0) printf("BDLENA time negative (%d)\n", bdl_ena); /* FIXME: calibration should be done just before BDLENA */ twl3025_tsp_write(BDLON); tpu_enq_wait(BDLON_TO_BDLCAL - TSP_DELAY); twl3025_tsp_write(BDLON | BDLCAL); tpu_enq_wait(BDLCAL_DURATION - TSP_DELAY); twl3025_tsp_write(BDLON); //tpu_enq_wait(BDLCAL_TO_BDLENA) this is only 3.7us == 4 qbits, i.e. less than the TSP_DELAY tpu_enq_at(bdl_ena); twl3025_tsp_write(BDLON | BDLENA); } else { tpu_enq_at(bdl_ena); twl3025_tsp_write(BDLON); //tpu_enq_wait(nBDLENA_TO_nBDLON) this is only 3.7us == 4 qbits, i.e. less than the TSP_DELAY twl3025_tsp_write(0); } } void twl3025_afc_set(int16_t val) { printf("twl3025_afc_set(%d)\n", val); if (val > 4095) val = 4095; else if (val <= -4096) val = -4096; /* FIXME: we currently write from the USP rather than BSP */ twl3025_reg_write(AUXAFC2, val >> 10); twl3025_reg_write(AUXAFC1, val & 0x3ff); } int16_t twl3025_afc_get(void) { int16_t val; val = (twl3025_reg_read(AUXAFC2) & 0x7); val = val << 10; val = val | (twl3025_reg_read(AUXAFC1) & 0x3ff); if (val > 4095) val = -(8192 - val); return val; } void twl3025_unit_enable(enum twl3025_unit unit, int on) { uint16_t togbr1 = 0; switch (unit) { case TWL3025_UNIT_AFC: if (on) togbr1 = (1 << 7); else togbr1 = (1 << 6); break; case TWL3025_UNIT_MAD: if (on) togbr1 = (1 << 9); else togbr1 = (1 << 8); break; case TWL3025_UNIT_ADA: if (on) togbr1 = (1 << 5); else togbr1 = (1 << 4); case TWL3025_UNIT_VDL: if (on) togbr1 = (1 << 3); else togbr1 = (1 << 2); break; case TWL3025_UNIT_VUL: if (on) togbr1 = (1 << 1); else togbr1 = (1 << 0); break; } twl3025_reg_write(TOGBR1, togbr1); } uint8_t twl3025_afcout_get(void) { return twl3025_reg_read(AFCOUT) & 0xff; } void twl3025_afcout_set(uint8_t val) { twl3025_reg_write(AFCCTLADD, 0x05); twl3025_reg_write(AFCOUT, val); }