summaryrefslogtreecommitdiffstats
path: root/firmware/drivers/pcf50606.c
diff options
context:
space:
mode:
authorJens Arnold <amiconn@rockbox.org>2006-04-16 23:16:32 +0000
committerJens Arnold <amiconn@rockbox.org>2006-04-16 23:16:32 +0000
commitf2897e81807bf66b5a391d82bca163d59008ccb0 (patch)
treef085f4e14e7f5a1a642dad52bfe43fc951faf672 /firmware/drivers/pcf50606.c
parent295c226ed507603a485e3524723f20741b9bdd7e (diff)
downloadrockbox-f2897e81807bf66b5a391d82bca163d59008ccb0.tar.gz
rockbox-f2897e81807bf66b5a391d82bca163d59008ccb0.zip
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
Diffstat (limited to 'firmware/drivers/pcf50606.c')
-rw-r--r--firmware/drivers/pcf50606.c418
1 files changed, 334 insertions, 84 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();