From f2897e81807bf66b5a391d82bca163d59008ccb0 Mon Sep 17 00:00:00 2001 From: Jens Arnold Date: Sun, 16 Apr 2006 23:16:32 +0000 Subject: H300: Optimised PCF50606 driver, significantly reduces CPU power drain from the button tick (with both main & remote buttons: 50%->13% at 11MHz, 12%->6% at 45MHz): * Delay is adapted to the current CPU clock, aiming at constant 400kHz i2c clock. * Reduced number of port accesses (accessing GPIO is very slow, especially with the atomic boolean instructions) by implementing an open-collector-like behaviour. * Time-critical functions implemented in assembler. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@9693 a1c6a512-1295-4272-9138-f99709370657 --- firmware/drivers/pcf50606.c | 418 +++++++++++++++++++++++++++++++++++--------- firmware/export/pcf50606.h | 1 + firmware/system.c | 12 +- 3 files changed, 346 insertions(+), 85 deletions(-) diff --git a/firmware/drivers/pcf50606.c b/firmware/drivers/pcf50606.c index 51cd812346..5c8f8d0e96 100644 --- a/firmware/drivers/pcf50606.c +++ b/firmware/drivers/pcf50606.c @@ -27,123 +27,373 @@ /* TODO: merge all bit-banged I2C into a generic I2C driver */ +#define USE_ASM -#define SDA_LO and_l(~0x00002000, &GPIO1_OUT) -#define SDA_HI or_l( 0x00002000, &GPIO1_OUT) -#define SDA_INPUT and_l(~0x00002000, &GPIO1_ENABLE) -#define SDA_OUTPUT or_l( 0x00002000, &GPIO1_ENABLE) #define SDA ( 0x00002000 & GPIO1_READ) +#define SDA_LO_OUT or_l( 0x00002000, &GPIO1_ENABLE) +#define SDA_HI_IN and_l(~0x00002000, &GPIO1_ENABLE) -/* SCL is GPIO, 3 */ -#define SCL_INPUT and_l(~0x00001000, &GPIO_ENABLE) -#define SCL_OUTPUT or_l( 0x00001000, &GPIO_ENABLE) -#define SCL_LO and_l(~0x00001000, &GPIO_OUT) -#define SCL_HI SCL_INPUT;while(!SCL){};or_l(0x1000, &GPIO_OUT);SCL_OUTPUT #define SCL ( 0x00001000 & GPIO_READ) +#define SCL_LO_OUT or_l( 0x00001000, &GPIO_ENABLE) +#define SCL_HI_IN and_l(~0x00001000, &GPIO_ENABLE); while(!SCL); -/* delay loop to achieve 400kHz at 120MHz CPU frequency */ -#define DELAY do { int _x; for(_x=0;_x<22;_x++);} while(0) +#define DELAY \ + asm ( \ + "move.l %[dly],%%d0 \n" \ + "1: \n" \ + "subq.l #1,%%d0 \n" \ + "bhi.s 1b \n" \ + : : [dly]"d"(i2c_delay) : "d0" ); -static void pcf50606_i2c_start(void) +static int i2c_delay IDATA_ATTR = 44; + +void pcf50606_i2c_recalc_delay(int cpu_clock) { - SDA_OUTPUT; - SCL_OUTPUT; - SDA_HI; - SCL_HI; + i2c_delay = MAX(cpu_clock / (400000*2*3) - 7, 1); +} + +static inline void pcf50606_i2c_start(void) +{ +#ifdef USE_ASM + asm ( + "not.l %[sdab] \n" /* SDA_HI_IN */ + "and.l %[sdab],(8,%[gpi1]) \n" + "not.l %[sdab] \n" + + "not.l %[sclb] \n" /* SCL_HI_IN */ + "and.l %[sclb],(8,%[gpio]) \n" + "not.l %[sclb] \n" + "1: \n" + "move.l (%[gpio]),%%d0 \n" + "btst.l #12,%%d0 \n" + "beq.s 1b \n" + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "or.l %[sdab],(8,%[gpi1]) \n" /* SDA_LO_OUT */ + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "or.l %[sclb],(8,%[gpio]) \n" /* SCL_LO_OUT */ + : /* outputs */ + : /* inputs */ + [gpio]"a"(&GPIO_READ), + [sclb]"d"(0x00001000), + [gpi1]"a"(&GPIO1_READ), + [sdab]"d"(0x00002000), + [dly] "d"(i2c_delay) + : /* clobbers */ + "d0" + ); +#else + SDA_HI_IN; + SCL_HI_IN; DELAY; - SDA_LO; + SDA_LO_OUT; DELAY; - SCL_LO; + SCL_LO_OUT; +#endif } -static void pcf50606_i2c_stop(void) +static inline void pcf50606_i2c_stop(void) { - SDA_LO; - SCL_HI; - DELAY; - SDA_HI; +#ifdef USE_ASM + asm ( + "or.l %[sdab],(8,%[gpi1]) \n" /* SDA_LO_OUT */ + + "not.l %[sclb] \n" /* SCL_HI_IN */ + "and.l %[sclb],(8,%[gpio]) \n" + "not.l %[sclb] \n" + "1: \n" + "move.l (%[gpio]),%%d0 \n" + "btst.l #12,%%d0 \n" + "beq.s 1b \n" + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "not.l %[sdab] \n" /* SDA_HI_IN */ + "and.l %[sdab],(8,%[gpi1]) \n" + "not.l %[sdab] \n" + : /* outputs */ + : /* inputs */ + [gpio]"a"(&GPIO_READ), + [sclb]"d"(0x00001000), + [gpi1]"a"(&GPIO1_READ), + [sdab]"d"(0x00002000), + [dly] "d"(i2c_delay) + : /* clobbers */ + "d0" + ); +#else + SDA_LO_OUT; + SCL_HI_IN; + DELAY; + SDA_HI_IN; +#endif } - -static void pcf50606_i2c_ack(bool ack) +static inline void pcf50606_i2c_ack(bool ack) { - SCL_LO; /* Set the clock low */ +#ifdef USE_ASM + asm ( + "tst.b %[ack] \n" /* if (!ack) */ + "bne.s 1f \n" + + "not.l %[sdab] \n" /* SDA_HI_IN */ + "and.l %[sdab],(8,%[gpi1]) \n" + "not.l %[sdab] \n" + ".word 0x51fb \n" /* trapf.l : else */ + "1: \n" + "or.l %[sdab],(8,%[gpi1]) \n" /* SDA_LO_OUT */ + + "not.l %[sclb] \n" /* SCL_HI_IN */ + "and.l %[sclb],(8,%[gpio]) \n" + "not.l %[sclb] \n" + "1: \n" + "move.l (%[gpio]),%%d0 \n" + "btst.l #12,%%d0 \n" + "beq.s 1b \n" + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "or.l %[sclb],(8,%[gpio]) \n" /* SCL_LO_OUT */ + : /* outputs */ + : /* inputs */ + [gpio]"a"(&GPIO_READ), + [sclb]"d"(0x00001000), + [gpi1]"a"(&GPIO1_READ), + [sdab]"d"(0x00002000), + [dly] "d"(i2c_delay), + [ack] "d"(ack) + : /* clobbers */ + "d0" + ); +#else if(ack) - SDA_LO; + SDA_LO_OUT; else - SDA_HI; + SDA_HI_IN; - SCL_HI; + SCL_HI_IN; DELAY; - SCL_OUTPUT; - SCL_LO; + SCL_LO_OUT; +#endif } -static int pcf50606_i2c_getack(void) +static inline bool pcf50606_i2c_getack(void) { - int ret = 1; - - SDA_INPUT; /* And set to input */ + bool ret; + +#ifdef USE_ASM + asm ( + "not.l %[sdab] \n" /* SDA_HI_IN */ + "and.l %[sdab],(8,%[gpi1]) \n" + "not.l %[sdab] \n" + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "not.l %[sclb] \n" /* SCL_HI_IN */ + "and.l %[sclb],(8,%[gpio]) \n" + "not.l %[sclb] \n" + "1: \n" + "move.l (%[gpio]),%%d0 \n" + "btst.l #12,%%d0 \n" + "beq.s 1b \n" + + "move.l (%[gpi1]),%%d0 \n" /* ret = !SDA */ + "btst.l #13,%%d0 \n" + "seq.b %[ret] \n" + + "or.l %[sclb],(8,%[gpio]) \n" /* SCL_LO_OUT */ + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + : /* outputs */ + [ret]"=&r"(ret) + : /* inputs */ + [gpio]"a"(&GPIO_READ), + [sclb]"d"(0x00001000), + [gpi1]"a"(&GPIO1_READ), + [sdab]"d"(0x00002000), + [dly] "d"(i2c_delay) + : /* clobbers */ + "d0" + ); +#else + SDA_HI_IN; DELAY; - SCL_HI; + SCL_HI_IN; - if (SDA) - /* ack failed */ - ret = 0; - - SCL_OUTPUT; - SCL_LO; - SDA_HI; - SDA_OUTPUT; + ret = !SDA; + + SCL_LO_OUT; DELAY; +#endif return ret; } static void pcf50606_i2c_outb(unsigned char byte) { - int i; - - /* clock out each bit, MSB first */ - for ( i=0x80; i; i>>=1 ) { - if ( i & byte ) - { - SDA_HI; - } - else - { - SDA_LO; - } - DELAY; - SCL_HI; - DELAY; - SCL_LO; - } - - SDA_HI; +#ifdef USE_ASM + asm volatile ( + "moveq.l #24,%%d0 \n" /* byte <<= 24 */ + "lsl.l %%d0,%[byte] \n" + "moveq.l #8,%%d1 \n" /* i = 8 */ + + "2: \n" /* do */ + "lsl.l #1,%[byte] \n" /* if ((byte <<= 1) carry) */ + "bcc.s 1f \n" + + "not.l %[sdab] \n" /* SDA_HI_IN */ + "and.l %[sdab],(8,%[gpi1]) \n" + "not.l %[sdab] \n" + ".word 0x51fb \n" /* trapf.l; else */ + "1: \n" + "or.l %[sdab],(8,%[gpi1]) \n" /* SDA_LO_OUT */ + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "not.l %[sclb] \n" /* SCL_HI_IN */ + "and.l %[sclb],(8,%[gpio]) \n" + "not.l %[sclb] \n" + "1: \n" + "move.l (%[gpio]),%%d0 \n" + "btst.l #12,%%d0 \n" + "beq.s 1b \n" + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "or.l %[sclb],(8,%[gpio]) \n" /* SCL_LO_OUT */ + + "subq.l #1,%%d1 \n" /* i-- */ + "bne.s 2b \n" /* while (i != 0) */ + : /* outputs */ + [byte]"+d"(byte) + : /* inputs */ + [gpio]"a"(&GPIO_READ), + [sclb]"d"(0x00001000), + [gpi1]"a"(&GPIO1_READ), + [sdab]"d"(0x00002000), + [dly] "d"(i2c_delay) + : /* clobbers */ + "d0", "d1" + ); +#else + int i; + + /* clock out each bit, MSB first */ + for ( i=0x80; i; i>>=1 ) + { + if ( i & byte ) + SDA_HI_IN; + else + SDA_LO_OUT; + DELAY; + SCL_HI_IN; + DELAY; + SCL_LO_OUT; + } +#endif } static unsigned char pcf50606_i2c_inb(bool ack) { - int i; - unsigned char byte = 0; - - /* clock in each bit, MSB first */ - for ( i=0x80; i; i>>=1 ) { - SDA_INPUT; /* And set to input */ - SCL_HI; - DELAY; - if ( SDA ) - byte |= i; - SCL_LO; - DELAY; - SDA_OUTPUT; - } - - pcf50606_i2c_ack(ack); + unsigned char byte = 0; + +#ifdef USE_ASM + asm ( + "not.l %[sdab] \n" /* SDA_HI_IN */ + "and.l %[sdab],(8,%[gpi1]) \n" + "not.l %[sdab] \n" + + "moveq.l #8,%%d1 \n" /* i = 8 */ + "clr.l %[byte] \n" /* byte = 0 */ + + "2: \n" /* do */ + "not.l %[sclb] \n" /* SCL_HI_IN */ + "and.l %[sclb],(8,%[gpio]) \n" + "not.l %[sclb] \n" + "1: \n" + "move.l (%[gpio]),%%d0 \n" + "btst.l #12,%%d0 \n" + "beq.s 1b \n" + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "lsl.l #1,%[byte] \n" /* byte <<= 1 */ + "move.l (%[gpi1]),%%d0 \n" /* if (SDA) */ + "btst.l #13,%%d0 \n" + "beq.s 1f \n" + "addq.l #1,%[byte] \n" /* byte++ */ + "1: \n" + + "or.l %[sclb],(8,%[gpio]) \n" /* SCL_LO_OUT */ + + "move.l %[dly],%%d0 \n" /* DELAY */ + "1: \n" + "subq.l #1,%%d0 \n" + "bhi.s 1b \n" + + "subq.l #1,%%d1 \n" /* i-- */ + "bne.s 2b \n" /* while (i != 0) */ + : /* outputs */ + [byte]"=&d"(byte) + : /* inputs */ + [gpio]"a"(&GPIO_READ), + [sclb]"d"(0x00001000), + [gpi1]"a"(&GPIO1_READ), + [sdab]"d"(0x00002000), + [dly] "d"(i2c_delay) + : /* clobbers */ + "d0", "d1" + ); +#else + int i; + + /* clock in each bit, MSB first */ + SDA_HI_IN; + for ( i=0x80; i; i>>=1 ) + { + SCL_HI_IN; + DELAY; + if ( SDA ) + byte |= i; + SCL_LO_OUT; + DELAY; + } +#endif + + pcf50606_i2c_ack(ack); - return byte; + return byte; } int pcf50606_i2c_write(int address, const unsigned char* buf, int count) @@ -270,12 +520,12 @@ static void set_voltages(void) void pcf50606_init(void) { /* Bit banged I2C */ - or_l(0x00002000, &GPIO1_OUT); - or_l(0x00001000, &GPIO_OUT); - or_l(0x00002000, &GPIO1_ENABLE); - or_l(0x00001000, &GPIO_ENABLE); or_l(0x00002000, &GPIO1_FUNCTION); or_l(0x00001000, &GPIO_FUNCTION); + and_l(~0x00002000, &GPIO1_OUT); + and_l(~0x00001000, &GPIO_OUT); + and_l(~0x00002000, &GPIO1_ENABLE); + and_l(~0x00001000, &GPIO_ENABLE); set_voltages(); diff --git a/firmware/export/pcf50606.h b/firmware/export/pcf50606.h index cbfaeb1135..d9f44bb577 100644 --- a/firmware/export/pcf50606.h +++ b/firmware/export/pcf50606.h @@ -20,6 +20,7 @@ #define PCF50606_H void pcf50606_init(void); +void pcf50606_i2c_recalc_delay(int cpu_clock); int pcf50606_write_multiple(int address, const unsigned char* buf, int count); int pcf50606_write(int address, unsigned char val); int pcf50606_read_multiple(int address, unsigned char* buf, int count); diff --git a/firmware/system.c b/firmware/system.c index 8bdd821e60..b41337b8fe 100644 --- a/firmware/system.c +++ b/firmware/system.c @@ -594,6 +594,13 @@ int system_memory_guard(int newmode) #define DEFAULT_REFRESH_TIMER 1 #endif +#ifdef IRIVER_H300_SERIES +#define RECALC_DELAYS(f) \ + pcf50606_i2c_recalc_delay(f) +#else +#define RECALC_DELAYS(f) +#endif + void set_cpu_frequency (long) __attribute__ ((section (".icode"))); void set_cpu_frequency(long frequency) { @@ -604,6 +611,7 @@ void set_cpu_frequency(long frequency) /* Refresh timer for bypass frequency */ PLLCR &= ~1; /* Bypass mode */ timers_adjust_prescale(CPUFREQ_DEFAULT_MULT, false); + RECALC_DELAYS(CPUFREQ_MAX); PLLCR = 0x11c56005; CSCR0 = 0x00001180; /* Flash: 4 wait states */ CSCR1 = 0x00000980; /* LCD: 2 wait states */ @@ -615,12 +623,13 @@ void set_cpu_frequency(long frequency) IDECONFIG1 = 0x106000 | (5 << 10); /* BUFEN2 enable + CS2Pre/CS2Post */ IDECONFIG2 = 0x40000 | (1 << 8); /* TA enable + CS2wait */ break; - + case CPUFREQ_NORMAL: DCR = (DCR & ~0x01ff) | DEFAULT_REFRESH_TIMER; /* Refresh timer for bypass frequency */ PLLCR &= ~1; /* Bypass mode */ timers_adjust_prescale(CPUFREQ_DEFAULT_MULT, false); + RECALC_DELAYS(CPUFREQ_NORMAL); PLLCR = 0x13c5e005; CSCR0 = 0x00000580; /* Flash: 1 wait state */ CSCR1 = 0x00000180; /* LCD: 0 wait states */ @@ -637,6 +646,7 @@ void set_cpu_frequency(long frequency) /* Refresh timer for bypass frequency */ PLLCR &= ~1; /* Bypass mode */ timers_adjust_prescale(CPUFREQ_DEFAULT_MULT, true); + RECALC_DELAYS(CPUFREQ_DEFAULT); PLLCR = 0x10c00200; /* Power down PLL, but keep CLSEL and CRSEL */ CSCR0 = 0x00000180; /* Flash: 0 wait states */ CSCR1 = 0x00000180; /* LCD: 0 wait states */ -- cgit v1.2.3