ARM: sa11x0: assabet: better reset handling
authorRussell King <rmk+kernel@arm.linux.org.uk>
Tue, 9 Jul 2013 09:27:12 +0000 (10:27 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Thu, 12 Dec 2013 22:59:15 +0000 (22:59 +0000)
The codec reset pin is connected to several peripherals.  When the
reset is released, unfortunately the ADV7171 powers itself up rather
than remaining in power-down mode.  As we don't have a driver for
this device, we end up needlessly consuming an additional 330mW.

Not only that but we should have a way to arbitrate the reset signal.

This patch provides that facility: we program the ADV7171 to sleep
mode whenever the reset is released, and we release the reset when
any one of the three peripherals requests the reset to be released.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
arch/arm/mach-sa1100/assabet.c
arch/arm/mach-sa1100/include/mach/assabet.h

index c9808c6841526204144e36a273596e6955696dc8..6a15354d43b81a2d7d8d71560cdf3cd80ccfa670 100644 (file)
@@ -75,11 +75,142 @@ void ASSABET_BCR_frob(unsigned int mask, unsigned int val)
 
 EXPORT_SYMBOL(ASSABET_BCR_frob);
 
+/*
+ * The codec reset goes to three devices, so we need to release
+ * the rest when any one of these requests it.  However, that
+ * causes the ADV7171 to consume around 100mA - more than half
+ * the LCD-blanked power.
+ *
+ * With the ADV7171, LCD and backlight enabled, we go over
+ * budget on the MAX846 Li-Ion charger, and if no Li-Ion battery
+ * is connected, the Assabet crashes.
+ */
+#define RST_UCB1X00 (1 << 0)
+#define RST_UDA1341 (1 << 1)
+#define RST_ADV7171 (1 << 2)
+
+#define SDA GPIO_GPIO(15)
+#define SCK GPIO_GPIO(18)
+#define MOD GPIO_GPIO(17)
+
+static void adv7171_start(void)
+{
+       GPSR = SCK;
+       udelay(1);
+       GPSR = SDA;
+       udelay(2);
+       GPCR = SDA;
+}
+
+static void adv7171_stop(void)
+{
+       GPSR = SCK;
+       udelay(2);
+       GPSR = SDA;
+       udelay(1);
+}
+
+static void adv7171_send(unsigned byte)
+{
+       unsigned i;
+
+       for (i = 0; i < 8; i++, byte <<= 1) {
+               GPCR = SCK;
+               udelay(1);
+               if (byte & 0x80)
+                       GPSR = SDA;
+               else
+                       GPCR = SDA;
+               udelay(1);
+               GPSR = SCK;
+               udelay(1);
+       }
+       GPCR = SCK;
+       udelay(1);
+       GPSR = SDA;
+       udelay(1);
+       GPDR &= ~SDA;
+       GPSR = SCK;
+       udelay(1);
+       if (GPLR & SDA)
+               printk(KERN_WARNING "No ACK from ADV7171\n");
+       udelay(1);
+       GPCR = SCK | SDA;
+       udelay(1);
+       GPDR |= SDA;
+       udelay(1);
+}
+
+static void adv7171_write(unsigned reg, unsigned val)
+{
+       unsigned gpdr = GPDR;
+       unsigned gplr = GPLR;
+
+       ASSABET_BCR = BCR_value | ASSABET_BCR_AUDIO_ON;
+       udelay(100);
+
+       GPCR = SDA | SCK | MOD; /* clear L3 mode to ensure UDA1341 doesn't respond */
+       GPDR = (GPDR | SCK | MOD) & ~SDA;
+       udelay(10);
+       if (!(GPLR & SDA))
+               printk(KERN_WARNING "Something dragging SDA down?\n");
+       GPDR |= SDA;
+
+       adv7171_start();
+       adv7171_send(0x54);
+       adv7171_send(reg);
+       adv7171_send(val);
+       adv7171_stop();
+
+       /* Restore GPIO state for L3 bus */
+       GPSR = gplr & (SDA | SCK | MOD);
+       GPCR = (~gplr) & (SDA | SCK | MOD);
+       GPDR = gpdr;
+}
+
+static void adv7171_sleep(void)
+{
+       /* Put the ADV7171 into sleep mode */
+       adv7171_write(0x04, 0x40);
+}
+
+static unsigned codec_nreset;
+
+static void assabet_codec_reset(unsigned mask, int set)
+{
+       unsigned long flags;
+       bool old;
+
+       local_irq_save(flags);
+       old = !codec_nreset;
+       if (set)
+               codec_nreset &= ~mask;
+       else
+               codec_nreset |= mask;
+
+       if (old != !codec_nreset) {
+               if (codec_nreset) {
+                       ASSABET_BCR_set(ASSABET_BCR_NCODEC_RST);
+                       adv7171_sleep();
+               } else {
+                       ASSABET_BCR_clear(ASSABET_BCR_NCODEC_RST);
+               }
+       }
+       local_irq_restore(flags);
+}
+
 static void assabet_ucb1x00_reset(enum ucb1x00_reset state)
 {
-       if (state == UCB_RST_PROBE)
-               ASSABET_BCR_set(ASSABET_BCR_CODEC_RST);
+       int set = state == UCB_RST_REMOVE || state == UCB_RST_SUSPEND ||
+               state == UCB_RST_PROBE_FAIL;
+       assabet_codec_reset(RST_UCB1X00, set);
+}
+
+void assabet_uda1341_reset(int set)
+{
+       assabet_codec_reset(RST_UDA1341, set);
 }
+EXPORT_SYMBOL(assabet_uda1341_reset);
 
 
 /*
index 307391488c22607c617d7931ed7867c3bdcd185d..c23fcdb047a5de3ea2260198ec6565a39626e059 100644 (file)
@@ -39,8 +39,8 @@ extern unsigned long SCR_value;
 
 #define ASSABET_BCR_CF_PWR     (1<<0)  /* Compact Flash Power (1 = 3.3v, 0 = off) */
 #define ASSABET_BCR_CF_RST     (1<<1)  /* Compact Flash Reset (1 = power up reset) */
-#define ASSABET_BCR_GFX_RST    (1<<1)  /* Graphics Accelerator Reset (0 = hold reset) */
-#define ASSABET_BCR_CODEC_RST  (1<<2)  /* 0 = Holds UCB1300, ADI7171, and UDA1341 in reset */
+#define ASSABET_BCR_NGFX_RST   (1<<1)  /* Graphics Accelerator Reset (0 = hold reset) */
+#define ASSABET_BCR_NCODEC_RST (1<<2)  /* 0 = Holds UCB1300, ADI7171, and UDA1341 in reset */
 #define ASSABET_BCR_IRDA_FSEL  (1<<3)  /* IRDA Frequency select (0 = SIR, 1 = MIR/ FIR) */
 #define ASSABET_BCR_IRDA_MD0   (1<<4)  /* Range/Power select */
 #define ASSABET_BCR_IRDA_MD1   (1<<5)  /* Range/Power select */
@@ -69,6 +69,8 @@ extern void ASSABET_BCR_frob(unsigned int mask, unsigned int set);
 #define ASSABET_BCR_frob(x,y)  do { } while (0)
 #endif
 
+extern void assabet_uda1341_reset(int set);
+
 #define ASSABET_BCR_set(x)     ASSABET_BCR_frob((x), (x))
 #define ASSABET_BCR_clear(x)   ASSABET_BCR_frob((x), 0)