via-cuda: Add support for Egret system controller
authorFinn Thain <fthain@telegraphics.com.au>
Sun, 1 Jan 2017 00:56:26 +0000 (19:56 -0500)
committerMichael Ellerman <mpe@ellerman.id.au>
Tue, 7 Feb 2017 05:56:24 +0000 (16:56 +1100)
The Egret system controller was the predecessor to the Cuda and the
differences are minor.

On Cuda, byte acknowledgement requires one transition of the TACK
signal; on Egret two are needed. On Cuda, TIP is active low; on Egret
it is active high. And Cuda raises certain interrupts that Egret omits.

Accomodating these differences complicates the Cuda driver slightly
but avoids a lot of duplication (see next patch).

Tested-by: Stan Johnson <userm57@yahoo.com>
Signed-off-by: Finn Thain <fthain@telegraphics.com.au>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
drivers/macintosh/via-cuda.c

index 57fb20dcb9dd7abbc651771822cde81bdab00b36..1a742bd9f612f16641a21ac5bbfd71bc88e97688 100644 (file)
@@ -1,10 +1,10 @@
 /*
- * Device driver for the via-cuda on Apple Powermacs.
+ * Device driver for the Cuda and Egret system controllers found on PowerMacs
+ * and 68k Macs.
  *
- * The VIA (versatile interface adapter) interfaces to the CUDA,
- * a 6805 microprocessor core which controls the ADB (Apple Desktop
- * Bus) which connects to the keyboard and mouse.  The CUDA also
- * controls system power and the RTC (real time clock) chip.
+ * The Cuda or Egret is a 6805 microcontroller interfaced to the 6522 VIA.
+ * This MCU controls system power, Parameter RAM, Real Time Clock and the
+ * Apple Desktop Bus (ADB) that connects to the keyboard and mouse.
  *
  * Copyright (C) 1996 Paul Mackerras.
  */
@@ -50,10 +50,27 @@ static DEFINE_SPINLOCK(cuda_lock);
 #define IER            (14*RS)         /* Interrupt enable register */
 #define ANH            (15*RS)         /* A-side data, no handshake */
 
-/* Bits in B data register: all active low */
-#define TREQ           0x08            /* Transfer request (input) */
-#define TACK           0x10            /* Transfer acknowledge (output) */
-#define TIP            0x20            /* Transfer in progress (output) */
+/*
+ * When the Cuda design replaced the Egret, some signal names and
+ * logic sense changed. They all serve the same purposes, however.
+ *
+ *   VIA pin       |  Egret pin
+ * ----------------+------------------------------------------
+ *   PB3 (input)   |  Transceiver session   (active low)
+ *   PB4 (output)  |  VIA full              (active high)
+ *   PB5 (output)  |  System session        (active high)
+ *
+ *   VIA pin       |  Cuda pin
+ * ----------------+------------------------------------------
+ *   PB3 (input)   |  Transfer request      (active low)
+ *   PB4 (output)  |  Byte acknowledge      (active low)
+ *   PB5 (output)  |  Transfer in progress  (active low)
+ */
+
+/* Bits in Port B data register */
+#define TREQ           0x08            /* Transfer request */
+#define TACK           0x10            /* Transfer acknowledge */
+#define TIP            0x20            /* Transfer in progress */
 
 /* Bits in ACR */
 #define SR_CTRL                0x1c            /* Shift register control bits */
@@ -65,6 +82,19 @@ static DEFINE_SPINLOCK(cuda_lock);
 #define IER_CLR                0               /* clear bits in IER */
 #define SR_INT         0x04            /* Shift register full/empty */
 
+/* Duration of byte acknowledgement pulse (us) */
+#define EGRET_TACK_ASSERTED_DELAY      300
+#define EGRET_TACK_NEGATED_DELAY       400
+
+/* Interval from interrupt to start of session (us) */
+#define EGRET_SESSION_DELAY            450
+
+#ifdef CONFIG_PPC
+#define mcu_is_egret   false
+#else
+static bool mcu_is_egret;
+#endif
+
 static inline bool TREQ_asserted(u8 portb)
 {
        return !(portb & TREQ);
@@ -72,12 +102,29 @@ static inline bool TREQ_asserted(u8 portb)
 
 static inline void assert_TIP(void)
 {
-       out_8(&via[B], in_8(&via[B]) & ~TIP);
+       if (mcu_is_egret) {
+               udelay(EGRET_SESSION_DELAY);
+               out_8(&via[B], in_8(&via[B]) | TIP);
+       } else
+               out_8(&via[B], in_8(&via[B]) & ~TIP);
+}
+
+static inline void assert_TIP_and_TACK(void)
+{
+       if (mcu_is_egret) {
+               udelay(EGRET_SESSION_DELAY);
+               out_8(&via[B], in_8(&via[B]) | TIP | TACK);
+       } else
+               out_8(&via[B], in_8(&via[B]) & ~(TIP | TACK));
 }
 
 static inline void assert_TACK(void)
 {
-       out_8(&via[B], in_8(&via[B]) & ~TACK);
+       if (mcu_is_egret) {
+               udelay(EGRET_TACK_NEGATED_DELAY);
+               out_8(&via[B], in_8(&via[B]) | TACK);
+       } else
+               out_8(&via[B], in_8(&via[B]) & ~TACK);
 }
 
 static inline void toggle_TACK(void)
@@ -87,12 +134,20 @@ static inline void toggle_TACK(void)
 
 static inline void negate_TACK(void)
 {
-       out_8(&via[B], in_8(&via[B]) | TACK);
+       if (mcu_is_egret) {
+               udelay(EGRET_TACK_ASSERTED_DELAY);
+               out_8(&via[B], in_8(&via[B]) & ~TACK);
+       } else
+               out_8(&via[B], in_8(&via[B]) | TACK);
 }
 
 static inline void negate_TIP_and_TACK(void)
 {
-       out_8(&via[B], in_8(&via[B]) | TIP | TACK);
+       if (mcu_is_egret) {
+               udelay(EGRET_TACK_ASSERTED_DELAY);
+               out_8(&via[B], in_8(&via[B]) & ~(TIP | TACK));
+       } else
+               out_8(&via[B], in_8(&via[B]) | TIP | TACK);
 }
 
 static enum cuda_state {
@@ -155,6 +210,7 @@ int __init find_via_cuda(void)
 
     via = via1;
     cuda_state = idle;
+    mcu_is_egret = false;
 
     err = cuda_init_via();
     if (err) {
@@ -251,7 +307,7 @@ static int __init via_cuda_start(void)
        return -EAGAIN;
     }
 
-    pr_info("Macintosh CUDA driver v0.5 for Unified ADB.\n");
+    pr_info("Macintosh Cuda and Egret driver.\n");
 
     cuda_fully_inited = 1;
     return 0;
@@ -276,6 +332,33 @@ cuda_probe(void)
 }
 #endif /* CONFIG_ADB */
 
+static int __init sync_egret(void)
+{
+       if (TREQ_asserted(in_8(&via[B]))) {
+               /* Complete the inbound transfer */
+               assert_TIP_and_TACK();
+               while (1) {
+                       negate_TACK();
+                       mdelay(1);
+                       (void)in_8(&via[SR]);
+                       assert_TACK();
+                       if (!TREQ_asserted(in_8(&via[B])))
+                               break;
+               }
+               negate_TIP_and_TACK();
+       } else if (in_8(&via[B]) & TIP) {
+               /* Terminate the outbound transfer */
+               negate_TACK();
+               assert_TACK();
+               mdelay(1);
+               negate_TIP_and_TACK();
+       }
+       /* Clear shift register interrupt */
+       if (in_8(&via[IFR]) & SR_INT)
+               (void)in_8(&via[SR]);
+       return 0;
+}
+
 #define WAIT_FOR(cond, what)                                   \
     do {                                                        \
        int x;                                                  \
@@ -291,10 +374,6 @@ cuda_probe(void)
 static int
 __init cuda_init_via(void)
 {
-    out_8(&via[DIRB], (in_8(&via[DIRB]) | TACK | TIP) & ~TREQ);        /* TACK & TIP out */
-    negate_TIP_and_TACK();
-    out_8(&via[ACR] ,(in_8(&via[ACR]) & ~SR_CTRL) | SR_EXT);   /* SR data in */
-    (void)in_8(&via[SR]);                                              /* clear any left-over data */
 #ifdef CONFIG_PPC
     out_8(&via[IER], 0x7f);                                    /* disable interrupts from VIA */
     (void)in_8(&via[IER]);
@@ -302,6 +381,15 @@ __init cuda_init_via(void)
     out_8(&via[IER], SR_INT);                                  /* disable SR interrupt from VIA */
 #endif
 
+    out_8(&via[DIRB], (in_8(&via[DIRB]) | TACK | TIP) & ~TREQ);        /* TACK & TIP out */
+    out_8(&via[ACR], (in_8(&via[ACR]) & ~SR_CTRL) | SR_EXT);   /* SR data in */
+    (void)in_8(&via[SR]);                                      /* clear any left-over data */
+
+    if (mcu_is_egret)
+       return sync_egret();
+
+    negate_TIP_and_TACK();
+
     /* delay 4ms and then clear any pending interrupt */
     mdelay(4);
     (void)in_8(&via[SR]);
@@ -453,7 +541,10 @@ cuda_start(void)
     /* set the shift register to shift out and send a byte */
     out_8(&via[ACR], in_8(&via[ACR]) | SR_OUT);
     out_8(&via[SR], current_req->data[data_index++]);
-    assert_TIP();
+    if (mcu_is_egret)
+       assert_TIP_and_TACK();
+    else
+       assert_TIP();
     cuda_state = sent_first_byte;
 }
 
@@ -500,8 +591,9 @@ cuda_interrupt(int irq, void *arg)
 
     switch (cuda_state) {
     case idle:
-       /* CUDA has sent us the first byte of data - unsolicited */
+       /* System controller has unsolicited data for us */
        (void)in_8(&via[SR]);
+idle_state:
        assert_TIP();
        cuda_state = reading;
        reply_ptr = cuda_rbuf;
@@ -509,7 +601,7 @@ cuda_interrupt(int irq, void *arg)
        break;
 
     case awaiting_reply:
-       /* CUDA has sent us the first byte of data of a reply */
+       /* System controller has reply data for us */
        (void)in_8(&via[SR]);
        assert_TIP();
        cuda_state = reading;
@@ -524,9 +616,14 @@ cuda_interrupt(int irq, void *arg)
            (void)in_8(&via[SR]);
            negate_TIP_and_TACK();
            cuda_state = idle;
+           /* Egret does not raise an "aborted" interrupt */
+           if (mcu_is_egret)
+               goto idle_state;
        } else {
            out_8(&via[SR], current_req->data[data_index++]);
            toggle_TACK();
+           if (mcu_is_egret)
+               assert_TACK();
            cuda_state = sending;
        }
        break;
@@ -550,6 +647,8 @@ cuda_interrupt(int irq, void *arg)
        } else {
            out_8(&via[SR], req->data[data_index++]);
            toggle_TACK();
+           if (mcu_is_egret)
+               assert_TACK();
        }
        break;
 
@@ -560,16 +659,24 @@ cuda_interrupt(int irq, void *arg)
        else
            *reply_ptr++ = in_8(&via[SR]);
        if (!TREQ_asserted(status)) {
+           if (mcu_is_egret)
+               assert_TACK();
            /* that's all folks */
            negate_TIP_and_TACK();
            cuda_state = read_done;
+           /* Egret does not raise a "read done" interrupt */
+           if (mcu_is_egret)
+               goto read_done_state;
        } else {
            toggle_TACK();
+           if (mcu_is_egret)
+               negate_TACK();
        }
        break;
 
     case read_done:
        (void)in_8(&via[SR]);
+read_done_state:
        if (reading_reply) {
            req = current_req;
            req->reply_len = reply_ptr - req->reply;
@@ -645,6 +752,12 @@ cuda_input(unsigned char *buf, int nb)
 #endif /* CONFIG_ADB */
        break;
 
+    case TIMER_PACKET:
+       /* Egret sends these periodically. Might be useful as a 'heartbeat'
+        * to trigger a recovery for the VIA shift register errata.
+        */
+       break;
+
     default:
        print_hex_dump(KERN_INFO, "cuda_input: ", DUMP_PREFIX_NONE, 32, 1,
                       buf, nb, false);