Partial SD card SPI mode support.
authorpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>
Sat, 24 Nov 2007 23:35:08 +0000 (23:35 +0000)
committerpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>
Sat, 24 Nov 2007 23:35:08 +0000 (23:35 +0000)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@3731 c046a42c-6fe2-441c-8c8c-71466251a162

12 files changed:
Makefile
Makefile.target
hw/omap_mmc.c
hw/pl061.c
hw/pl181.c
hw/primecell.h
hw/pxa2xx_mmci.c
hw/sd.c
hw/sd.h
hw/ssd0323.c
hw/ssi-sd.c [new file with mode: 0644]
hw/stellaris.c

index c262c47..a2177c1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -57,6 +57,7 @@ OBJS+=i2c.o smbus.o smbus_eeprom.o max7310.o max111x.o wm8750.o
 OBJS+=ssd0303.o ssd0323.o ads7846.o stellaris_input.o
 OBJS+=scsi-disk.o cdrom.o
 OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o
+OBJS+=sd.o ssi-sd.o
 
 ifdef CONFIG_WIN32
 OBJS+=tap-win32.o
index 52d44a4..33921c5 100644 (file)
@@ -488,7 +488,7 @@ endif
 ifeq ($(TARGET_BASE_ARCH), arm)
 VL_OBJS+= integratorcp.o versatilepb.o ps2.o smc91c111.o arm_pic.o arm_timer.o
 VL_OBJS+= arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
-VL_OBJS+= versatile_pci.o sd.o ptimer.o
+VL_OBJS+= versatile_pci.o ptimer.o
 VL_OBJS+= realview_gic.o realview.o arm_sysctl.o mpcore.o
 VL_OBJS+= armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 VL_OBJS+= pl061.o
index 0a7ae87..6fbbb84 100644 (file)
@@ -525,7 +525,7 @@ struct omap_mmc_s *omap_mmc_init(target_phys_addr_t base,
     cpu_register_physical_memory(s->base, 0x800, iomemtype);
 
     /* Instantiate the storage */
-    s->card = sd_init(bd);
+    s->card = sd_init(bd, 0);
 
     return s;
 }
index d9a6f50..3ac0a4c 100644 (file)
@@ -48,6 +48,7 @@ typedef struct {
     uint8_t slr;
     uint8_t den;
     uint8_t cr;
+    uint8_t float_high;
     qemu_irq irq;
     qemu_irq out[8];
 } pl061_state;
@@ -56,18 +57,22 @@ static void pl061_update(pl061_state *s)
 {
     uint8_t changed;
     uint8_t mask;
+    uint8_t out;
     int i;
 
-    changed = s->old_data ^ s->data;
+    /* Outputs float high.  */
+    /* FIXME: This is board dependent.  */
+    out = (s->data & s->dir) | ~s->dir;
+    changed = s->old_data ^ out;
     if (!changed)
         return;
 
-    s->old_data = s->data;
+    s->old_data = out;
     for (i = 0; i < 8; i++) {
         mask = 1 << i;
-        if ((changed & mask & s->dir) && s->out) {
-            DPRINTF("Set output %d = %d\n", i, (s->data & mask) != 0);
-            qemu_set_irq(s->out[i], (s->data & mask) != 0);
+        if ((changed & mask) && s->out) {
+            DPRINTF("Set output %d = %d\n", i, (out & mask) != 0);
+            qemu_set_irq(s->out[i], (out & mask) != 0);
         }
     }
 
index 5eb46d0..75c3143 100644 (file)
@@ -458,7 +458,7 @@ void pl181_init(uint32_t base, BlockDriverState *bd,
                                        pl181_writefn, s);
     cpu_register_physical_memory(base, 0x00001000, iomemtype);
     s->base = base;
-    s->card = sd_init(bd);
+    s->card = sd_init(bd, 0);
     s->irq[0] = irq0;
     s->irq[1] = irq1;
     qemu_register_reset(pl181_reset, s);
index 072390b..aa35adc 100644 (file)
@@ -21,13 +21,15 @@ void pl011_init(uint32_t base, qemu_irq irq, CharDriverState *chr,
                 enum pl011_type type);
 
 /* pl022.c */
-void pl022_init(uint32_t base, qemu_irq irq, int (*xfer_cb)(void *, int),
+typedef int (*ssi_xfer_cb)(void *, int);
+void pl022_init(uint32_t base, qemu_irq irq, ssi_xfer_cb xfer_cb,
                 void *opaque);
 
 /* pl050.c */
 void pl050_init(uint32_t base, qemu_irq irq, int is_mouse);
 
 /* pl061.c */
+void pl061_float_high(void *opaque, uint8_t mask);
 qemu_irq *pl061_init(uint32_t base, qemu_irq irq, qemu_irq **out);
 
 /* pl080.c */
index 9d26b79..32b6a6f 100644 (file)
@@ -538,7 +538,7 @@ struct pxa2xx_mmci_s *pxa2xx_mmci_init(target_phys_addr_t base,
     cpu_register_physical_memory(base, 0x00100000, iomemtype);
 
     /* Instantiate the actual storage */
-    s->card = sd_init(bd);
+    s->card = sd_init(bd, 1);
 
     register_savevm("pxa2xx_mmci", 0, 0,
                     pxa2xx_mmci_save, pxa2xx_mmci_load, s);
diff --git a/hw/sd.c b/hw/sd.c
index 8b481e6..fc3c45a 100644 (file)
--- a/hw/sd.c
+++ b/hw/sd.c
@@ -87,6 +87,7 @@ struct SDState {
     int pwd_len;
     int function_group[6];
 
+    int spi;
     int current_cmd;
     int blk_written;
     uint32_t data_start;
@@ -395,11 +396,16 @@ static void sd_cardchange(void *opaque)
     }
 }
 
-SDState *sd_init(BlockDriverState *bs)
+/* We do not model the chip select pin, so allow the board to select
+   whether card should be in SSI ot MMC/SD mode.  It is also up to the
+   board to ensure that ssi transfers only occur when the chip select
+   is asserted.  */
+SDState *sd_init(BlockDriverState *bs, int is_spi)
 {
     SDState *sd;
 
     sd = (SDState *) qemu_mallocz(sizeof(SDState));
+    sd->spi = is_spi;
     sd_reset(sd, bs);
     bdrv_set_change_cb(sd->bdrv, sd_cardchange, sd);
     return sd;
@@ -567,16 +573,25 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
     case 0:    /* CMD0:   GO_IDLE_STATE */
         switch (sd->state) {
         case sd_inactive_state:
-            return sd_r0;
+            return sd->spi ? sd_r1 : sd_r0;
 
         default:
             sd->state = sd_idle_state;
             sd_reset(sd, sd->bdrv);
-            return sd_r0;
+            return sd->spi ? sd_r1 : sd_r0;
         }
         break;
 
+    case 1:    /* CMD1:   SEND_OP_CMD */
+        if (!sd->spi)
+            goto bad_cmd;
+
+        sd->state = sd_transfer_state;
+        return sd_r1;
+
     case 2:    /* CMD2:   ALL_SEND_CID */
+        if (sd->spi)
+            goto bad_cmd;
         switch (sd->state) {
         case sd_ready_state:
             sd->state = sd_identification_state;
@@ -588,6 +603,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     case 3:    /* CMD3:   SEND_RELATIVE_ADDR */
+        if (sd->spi)
+            goto bad_cmd;
         switch (sd->state) {
         case sd_identification_state:
         case sd_standby_state:
@@ -601,6 +618,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     case 4:    /* CMD4:   SEND_DSR */
+        if (sd->spi)
+            goto bad_cmd;
         switch (sd->state) {
         case sd_standby_state:
             break;
@@ -611,6 +630,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     case 6:    /* CMD6:   SWITCH_FUNCTION */
+        if (sd->spi)
+            goto bad_cmd;
         switch (sd->mode) {
         case sd_data_transfer_mode:
             sd_function_switch(sd, req.arg);
@@ -625,6 +646,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     case 7:    /* CMD7:   SELECT/DESELECT_CARD */
+        if (sd->spi)
+            goto bad_cmd;
         switch (sd->state) {
         case sd_standby_state:
             if (sd->rca != rca)
@@ -668,6 +691,15 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
 
             return sd_r2_s;
 
+        case sd_transfer_state:
+            if (!sd->spi)
+                break;
+            sd->state = sd_sendingdata_state;
+            memcpy(sd->data, sd->csd, 16);
+            sd->data_start = req.arg;
+            sd->data_offset = 0;
+            return sd_r1;
+
         default:
             break;
         }
@@ -681,12 +713,23 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
 
             return sd_r2_i;
 
+        case sd_transfer_state:
+            if (!sd->spi)
+                break;
+            sd->state = sd_sendingdata_state;
+            memcpy(sd->data, sd->cid, 16);
+            sd->data_start = req.arg;
+            sd->data_offset = 0;
+            return sd_r1;
+
         default:
             break;
         }
         break;
 
     case 11:   /* CMD11:  READ_DAT_UNTIL_STOP */
+        if (sd->spi)
+            goto bad_cmd;
         switch (sd->state) {
         case sd_transfer_state:
             sd->state = sd_sendingdata_state;
@@ -733,6 +776,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     case 15:   /* CMD15:  GO_INACTIVE_STATE */
+        if (sd->spi)
+            goto bad_cmd;
         switch (sd->mode) {
         case sd_data_transfer_mode:
             if (sd->rca != rca)
@@ -796,8 +841,13 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
 
     /* Block write commands (Class 4) */
     case 24:   /* CMD24:  WRITE_SINGLE_BLOCK */
+        if (sd->spi)
+            goto unimplemented_cmd;
         switch (sd->state) {
         case sd_transfer_state:
+            /* Writing in SPI mode not implemented.  */
+            if (sd->spi)
+                break;
             sd->state = sd_receivingdata_state;
             sd->data_start = req.arg;
             sd->data_offset = 0;
@@ -817,8 +867,13 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     case 25:   /* CMD25:  WRITE_MULTIPLE_BLOCK */
+        if (sd->spi)
+            goto unimplemented_cmd;
         switch (sd->state) {
         case sd_transfer_state:
+            /* Writing in SPI mode not implemented.  */
+            if (sd->spi)
+                break;
             sd->state = sd_receivingdata_state;
             sd->data_start = req.arg;
             sd->data_offset = 0;
@@ -838,6 +893,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     case 26:   /* CMD26:  PROGRAM_CID */
+        if (sd->spi)
+            goto bad_cmd;
         switch (sd->state) {
         case sd_transfer_state:
             sd->state = sd_receivingdata_state;
@@ -851,6 +908,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     case 27:   /* CMD27:  PROGRAM_CSD */
+        if (sd->spi)
+            goto unimplemented_cmd;
         switch (sd->state) {
         case sd_transfer_state:
             sd->state = sd_receivingdata_state;
@@ -962,6 +1021,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
 
     /* Lock card commands (Class 7) */
     case 42:   /* CMD42:  LOCK_UNLOCK */
+        if (sd->spi)
+            goto unimplemented_cmd;
         switch (sd->state) {
         case sd_transfer_state:
             sd->state = sd_receivingdata_state;
@@ -1000,10 +1061,17 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         break;
 
     default:
+    bad_cmd:
         sd->card_status |= ILLEGAL_COMMAND;
 
         printf("SD: Unknown CMD%i\n", req.cmd);
         return sd_r0;
+
+    unimplemented_cmd:
+        /* Commands that are recognised but not yet implemented in SPI mode.  */
+        sd->card_status |= ILLEGAL_COMMAND;
+        printf ("SD: CMD%i not implemented in SPI mode\n", req.cmd);
+        return sd_r0;
     }
 
     sd->card_status |= ILLEGAL_COMMAND;
@@ -1069,6 +1137,11 @@ static sd_rsp_type_t sd_app_command(SDState *sd,
         break;
 
     case 41:   /* ACMD41: SD_APP_OP_COND */
+        if (sd->spi) {
+            /* SEND_OP_CMD */
+            sd->state = sd_transfer_state;
+            return sd_r1;
+        }
         switch (sd->state) {
         case sd_idle_state:
             /* We accept any voltage.  10000 V is nothing.  */
@@ -1414,6 +1487,14 @@ uint8_t sd_read_data(SDState *sd)
             sd->state = sd_transfer_state;
         break;
 
+    case 9:    /* CMD9:   SEND_CSD */
+    case 10:   /* CMD10:  SEND_CID */
+        ret = sd->data[sd->data_offset ++];
+
+        if (sd->data_offset >= 16)
+            sd->state = sd_transfer_state;
+        break;
+
     case 11:   /* CMD11:  READ_DAT_UNTIL_STOP */
         if (sd->data_offset == 0)
             BLK_READ_BLOCK(sd->data_start, sd->blk_len);
diff --git a/hw/sd.h b/hw/sd.h
index 9416df0..85f110f 100644 (file)
--- a/hw/sd.h
+++ b/hw/sd.h
@@ -67,7 +67,7 @@ struct sd_request_s {
 
 typedef struct SDState SDState;
 
-SDState *sd_init(BlockDriverState *bs);
+SDState *sd_init(BlockDriverState *bs, int is_spi);
 int sd_do_command(SDState *sd, struct sd_request_s *req,
                   uint8_t *response);
 void sd_write_data(SDState *sd, uint8_t value);
@@ -75,4 +75,8 @@ uint8_t sd_read_data(SDState *sd);
 void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert);
 int sd_data_ready(SDState *sd);
 
+/* ssi-sd.c */
+int ssi_sd_xfer(void *opaque, int val);
+void *ssi_sd_init(BlockDriverState *bs);
+
 #endif /* __hw_sd_h */
index c31e49a..4706b05 100644 (file)
@@ -157,6 +157,9 @@ int ssd0323_xfer_ssi(void *opaque, int data)
         case 0xe3: /* NOP.  */
             DATA(0);
             break;
+        case 0xff: /* Nasty hack because we don't handle chip selects
+                      properly.  */
+            break;
         default:
             BADF("Unknown command: 0x%x\n", data);
         }
diff --git a/hw/ssi-sd.c b/hw/ssi-sd.c
new file mode 100644 (file)
index 0000000..8b45fc4
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * SSI to SD card adapter.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licenced under the GPL.
+ */
+
+#include "hw.h"
+#include "sd.h"
+
+//#define DEBUG_SSI_SD 1
+
+#ifdef DEBUG_SSI_SD
+#define DPRINTF(fmt, args...) \
+do { printf("ssi_sd: " fmt , ##args); } while (0)
+#define BADF(fmt, args...) \
+do { fprintf(stderr, "ssi_sd: error: " fmt , ##args); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while(0)
+#define BADF(fmt, args...) \
+do { fprintf(stderr, "ssi_sd: error: " fmt , ##args);} while (0)
+#endif
+
+typedef enum {
+    SSI_SD_CMD,
+    SSI_SD_CMDARG,
+    SSI_SD_RESPONSE,
+    SSI_SD_DATA_START,
+    SSI_SD_DATA_READ,
+} ssi_sd_mode;
+
+typedef struct {
+    ssi_sd_mode mode;
+    int cmd;
+    uint8_t cmdarg[4];
+    uint8_t response[5];
+    int arglen;
+    int response_pos;
+    int stopping;
+    SDState *sd;
+} ssi_sd_state;
+
+/* State word bits.  */
+#define SSI_SDR_LOCKED          0x0001
+#define SSI_SDR_WP_ERASE        0x0002
+#define SSI_SDR_ERROR           0x0004
+#define SSI_SDR_CC_ERROR        0x0008
+#define SSI_SDR_ECC_FAILED      0x0010
+#define SSI_SDR_WP_VIOLATION    0x0020
+#define SSI_SDR_ERASE_PARAM     0x0040
+#define SSI_SDR_OUT_OF_RANGE    0x0080
+#define SSI_SDR_IDLE            0x0100
+#define SSI_SDR_ERASE_RESET     0x0200
+#define SSI_SDR_ILLEGAL_COMMAND 0x0400
+#define SSI_SDR_COM_CRC_ERROR   0x0800
+#define SSI_SDR_ERASE_SEQ_ERROR 0x1000
+#define SSI_SDR_ADDRESS_ERROR   0x2000
+#define SSI_SDR_PARAMETER_ERROR 0x4000
+
+int ssi_sd_xfer(void *opaque, int val)
+{
+    ssi_sd_state *s = (ssi_sd_state *)opaque;
+
+    /* Special case: allow CMD12 (STOP TRANSMISSION) while reading data.  */
+    if (s->mode == SSI_SD_DATA_READ && val == 0x4d) {
+        s->mode = SSI_SD_CMD;
+        /* There must be at least one byte delay before the card responds.  */
+        s->stopping = 1;
+    }
+
+    switch (s->mode) {
+    case SSI_SD_CMD:
+        if (val == 0xff) {
+            DPRINTF("NULL command\n");
+            return 0xff;
+        }
+        s->cmd = val & 0x3f;
+        s->mode = SSI_SD_CMDARG;
+        s->arglen = 0;
+        return 0xff;
+    case SSI_SD_CMDARG:
+        if (s->arglen == 4) {
+            struct sd_request_s request;
+            uint8_t longresp[16];
+            /* FIXME: Check CRC.  */
+            request.cmd = s->cmd;
+            request.arg = (s->cmdarg[0] << 24) | (s->cmdarg[1] << 16)
+                           | (s->cmdarg[2] << 8) | s->cmdarg[3];
+            DPRINTF("CMD%d arg 0x%08x\n", s->cmd, request.arg);
+            s->arglen = sd_do_command(s->sd, &request, longresp);
+            if (s->arglen <= 0) {
+                s->arglen = 1;
+                s->response[0] = 4;
+                DPRINTF("SD command failed\n");
+            } else if (s->cmd == 58) {
+                /* CMD58 returns R3 response (OCR)  */
+                DPRINTF("Returned OCR\n");
+                s->arglen = 5;
+                s->response[0] = 1;
+                memcpy(&s->response[1], longresp, 4);
+            } else if (s->arglen != 4) {
+                BADF("Unexpected response to cmd %d\n", s->cmd);
+                /* Illegal command is about as near as we can get.  */
+                s->arglen = 1;
+                s->response[0] = 4;
+            } else {
+                /* All other commands return status.  */
+                uint32_t cardstatus;
+                uint16_t status;
+                /* CMD13 returns a 2-byte statuse work. Other commands
+                   only return the first byte.  */
+                s->arglen = (s->cmd == 13) ? 2 : 1;
+                cardstatus = (longresp[0] << 24) | (longresp[1] << 16)
+                             | (longresp[2] << 8) | longresp[3];
+                status = 0;
+                if (((cardstatus >> 9) & 0xf) < 4)
+                    status |= SSI_SDR_IDLE;
+                if (cardstatus & ERASE_RESET)
+                    status |= SSI_SDR_ERASE_RESET;
+                if (cardstatus & ILLEGAL_COMMAND)
+                    status |= SSI_SDR_ILLEGAL_COMMAND;
+                if (cardstatus & COM_CRC_ERROR)
+                    status |= SSI_SDR_COM_CRC_ERROR;
+                if (cardstatus & ERASE_SEQ_ERROR)
+                    status |= SSI_SDR_ERASE_SEQ_ERROR;
+                if (cardstatus & ADDRESS_ERROR)
+                    status |= SSI_SDR_ADDRESS_ERROR;
+                if (cardstatus & CARD_IS_LOCKED)
+                    status |= SSI_SDR_LOCKED;
+                if (cardstatus & (LOCK_UNLOCK_FAILED | WP_ERASE_SKIP))
+                    status |= SSI_SDR_WP_ERASE;
+                if (cardstatus & SD_ERROR)
+                    status |= SSI_SDR_ERROR;
+                if (cardstatus & CC_ERROR)
+                    status |= SSI_SDR_CC_ERROR;
+                if (cardstatus & CARD_ECC_FAILED)
+                    status |= SSI_SDR_ECC_FAILED;
+                if (cardstatus & WP_VIOLATION)
+                    status |= SSI_SDR_WP_VIOLATION;
+                if (cardstatus & ERASE_PARAM)
+                    status |= SSI_SDR_ERASE_PARAM;
+                if (cardstatus & (OUT_OF_RANGE | CID_CSD_OVERWRITE))
+                    status |= SSI_SDR_OUT_OF_RANGE;
+                /* ??? Don't know what Parameter Error really means, so
+                   assume it's set if the second byte is nonzero.  */
+                if (status & 0xff)
+                    status |= SSI_SDR_PARAMETER_ERROR;
+                s->response[0] = status >> 8;
+                s->response[1] = status;
+                DPRINTF("Card status 0x%02x\n", status);
+            }
+            s->mode = SSI_SD_RESPONSE;
+            s->response_pos = 0;
+        } else {
+            s->cmdarg[s->arglen++] = val;
+        }
+        return 0xff;
+    case SSI_SD_RESPONSE:
+        if (s->stopping) {
+            s->stopping = 0;
+            return 0xff;
+        }
+        if (s->response_pos < s->arglen) {
+            DPRINTF("Response 0x%02x\n", s->response[s->response_pos]);
+            return s->response[s->response_pos++];
+        }
+        if (sd_data_ready(s->sd)) {
+            DPRINTF("Data read\n");
+            s->mode = SSI_SD_DATA_START;
+        } else {
+            DPRINTF("End of command\n");
+            s->mode = SSI_SD_CMD;
+        }
+        return 0xff;
+    case SSI_SD_DATA_START:
+        DPRINTF("Start read block\n");
+        s->mode = SSI_SD_DATA_READ;
+        return 0xfe;
+    case SSI_SD_DATA_READ:
+        val = sd_read_data(s->sd);
+        if (!sd_data_ready(s->sd)) {
+            DPRINTF("Data read end\n");
+            s->mode = SSI_SD_CMD;
+        }
+        return val;
+    }
+    /* Should never happen.  */
+    return 0xff;
+}
+
+void *ssi_sd_init(BlockDriverState *bs)
+{
+    ssi_sd_state *s;
+
+    s = (ssi_sd_state *)qemu_mallocz(sizeof(ssi_sd_state));
+    s->mode = SSI_SD_CMD;
+    s->sd = sd_init(bs, 1);
+    return s;
+}
+
index 01ed374..5f06388 100644 (file)
@@ -14,6 +14,7 @@
 #include "qemu-timer.h"
 #include "i2c.h"
 #include "net.h"
+#include "sd.h"
 #include "sysemu.h"
 #include "boards.h"
 
@@ -1000,6 +1001,51 @@ static qemu_irq stellaris_adc_init(uint32_t base, qemu_irq irq)
     return qi[0];
 }
 
+/* Some boards have both an OLED controller and SD card connected to
+   the same SSI port, with the SD card chip select connected to a
+   GPIO pin.  Technically the OLED chip select is connected to the SSI
+   Fss pin.  We do not bother emulating that as both devices should
+   never be selected simultaneously, and our OLED controller ignores stray
+   0xff commands that occur when deselecting the SD card.  */
+
+typedef struct {
+    ssi_xfer_cb xfer_cb[2];
+    void *opaque[2];
+    qemu_irq irq;
+    int current_dev;
+} stellaris_ssi_bus_state;
+
+static void stellaris_ssi_bus_select(void *opaque, int irq, int level)
+{
+    stellaris_ssi_bus_state *s = (stellaris_ssi_bus_state *)opaque;
+
+    s->current_dev = level;
+}
+
+static int stellaris_ssi_bus_xfer(void *opaque, int val)
+{
+    stellaris_ssi_bus_state *s = (stellaris_ssi_bus_state *)opaque;
+
+    return s->xfer_cb[s->current_dev](s->opaque[s->current_dev], val);
+}
+
+static void *stellaris_ssi_bus_init(qemu_irq *irqp,
+                                    ssi_xfer_cb cb0, void *opaque0,
+                                    ssi_xfer_cb cb1, void *opaque1)
+{
+    qemu_irq *qi;
+    stellaris_ssi_bus_state *s;
+
+    s = (stellaris_ssi_bus_state *)qemu_mallocz(sizeof(stellaris_ssi_bus_state));
+    s->xfer_cb[0] = cb0;
+    s->opaque[0] = opaque0;
+    s->xfer_cb[1] = cb1;
+    s->opaque[1] = opaque1;
+    qi = qemu_allocate_irqs(stellaris_ssi_bus_select, s, 1);
+    *irqp = *qi;
+    return s;
+}
+
 /* Board init.  */
 static stellaris_board_info stellaris_boards[] = {
   { "LM3S811EVB",
@@ -1085,9 +1131,19 @@ static void stellaris_init(const char *kernel_filename, const char *cpu_model,
     if (board->dc2 & (1 << 4)) {
         if (board->peripherals & BP_OLED_SSI) {
             void * oled;
-            /* FIXME: Implement chip select for OLED/MMC.  */
+            void * sd;
+            void *ssi_bus;
+
             oled = ssd0323_init(ds, &gpio_out[GPIO_C][7]);
-            pl022_init(0x40008000, pic[7], ssd0323_xfer_ssi, oled);
+            sd = ssi_sd_init(sd_bdrv);
+
+            ssi_bus = stellaris_ssi_bus_init(&gpio_out[GPIO_D][0],
+                                             ssi_sd_xfer, sd,
+                                             ssd0323_xfer_ssi, oled);
+
+            pl022_init(0x40008000, pic[7], stellaris_ssi_bus_xfer, ssi_bus);
+            /* Make sure the select pin is high.  */
+            qemu_irq_raise(gpio_out[GPIO_D][0]);
         } else {
             pl022_init(0x40008000, pic[7], NULL, NULL);
         }