diff --git a/include/tinysd/sdio.h b/include/tinysd/sdio.h index 29c2337..13aca5d 100644 --- a/include/tinysd/sdio.h +++ b/include/tinysd/sdio.h @@ -79,6 +79,10 @@ #define TSD_SDIO_BUS_WIDTH_4BIT 2 #define TSD_SDIO_BUS_WIDTH_8BIT 3 +/** Card capability register fields. */ +#define TSD_SDIO_CAPABILITY_4BLS TSD_BIT(7) +#define TSD_SDIO_CAPABILITY_LSC TSD_BIT(6) + /** Bus speed select register fields. */ #define TSD_SDIO_SPEED_EHS TSD_BIT(1) #define TSD_SDIO_SPEED_SHS TSD_BIT(0) @@ -149,7 +153,7 @@ int tsd_sdio_identify(struct tsd_host *host, struct tsd_sdio_cis *cis); * * @param card the card * @param host the host the card sits on, started by the caller - * @param width the bus width, 1 or 4 + * @param width the bus width the host can run, 1 or 4 * @param clock_khz the highest clock in kHz, held lower when the card cannot run it * @return 0 or a negative tsd_error */ diff --git a/src/sdio_init.c b/src/sdio_init.c index 5544237..dac30f5 100644 --- a/src/sdio_init.c +++ b/src/sdio_init.c @@ -227,13 +227,28 @@ static int enumerate_card(struct tsd_sdio *card, struct tsd_host *host) // Switch the card and then the host between 1-bit and 4-bit mode static int set_bus_width(struct tsd_sdio *card, uint8_t width) { + // Read the card capability register + uint8_t capability; + int ret = tsd_sdio_read_f0(card, TSD_SDIO_CCCR_CARD_CAPABILITY, &capability); + if (ret < 0) + return ret; + + // Keep a low speed card on one data line unless it supports four + if ((capability & TSD_SDIO_CAPABILITY_LSC) && !(capability & TSD_SDIO_CAPABILITY_4BLS)) + width = 1; + + // Pick the width field's value + uint8_t field = TSD_SDIO_BUS_WIDTH_1BIT; + if (width == 4) + field = TSD_SDIO_BUS_WIDTH_4BIT; + // Read the bus interface control register and set its width field uint8_t interface; - int ret = tsd_sdio_read_f0(card, TSD_SDIO_CCCR_BUS_INTERFACE_CONTROL, &interface); + ret = tsd_sdio_read_f0(card, TSD_SDIO_CCCR_BUS_INTERFACE_CONTROL, &interface); if (ret < 0) return ret; - interface = (interface & ~TSD_SDIO_BUS_WIDTH) | - TSD_FIELD_PREP(TSD_SDIO_BUS_WIDTH, width == 4 ? TSD_SDIO_BUS_WIDTH_4BIT : TSD_SDIO_BUS_WIDTH_1BIT); + interface &= ~TSD_SDIO_BUS_WIDTH; + interface |= TSD_FIELD_PREP(TSD_SDIO_BUS_WIDTH, field); // Switch the card first, so it is listening on four lines when the host starts driving them ret = tsd_sdio_write_f0(card, TSD_SDIO_CCCR_BUS_INTERFACE_CONTROL, interface); diff --git a/test/test_sdio.c b/test/test_sdio.c index cad0b38..0102f18 100644 --- a/test/test_sdio.c +++ b/test/test_sdio.c @@ -183,6 +183,20 @@ static void test_low_speed(void) TEST_ASSERT_EQUAL_UINT32(400, host.clock_khz); } +// Test that a low speed card stays on one data line unless it supports four +static void test_low_speed_width(void) +{ + uint8_t *interface = &host.f0[TSD_SDIO_CCCR_BUS_INTERFACE_CONTROL]; + + host.f0[TSD_SDIO_CCCR_CARD_CAPABILITY] = TSD_SDIO_CAPABILITY_LSC; + TEST_ASSERT_EQUAL(0, tsd_sdio_init(&card, &host.base, 4, 400)); + TEST_ASSERT_EQUAL(TSD_SDIO_BUS_WIDTH_1BIT, TSD_FIELD_GET(TSD_SDIO_BUS_WIDTH, *interface)); + + host.f0[TSD_SDIO_CCCR_CARD_CAPABILITY] = TSD_SDIO_CAPABILITY_LSC | TSD_SDIO_CAPABILITY_4BLS; + TEST_ASSERT_EQUAL(0, tsd_sdio_init(&card, &host.base, 4, 400)); + TEST_ASSERT_EQUAL(TSD_SDIO_BUS_WIDTH_4BIT, TSD_FIELD_GET(TSD_SDIO_BUS_WIDTH, *interface)); +} + // Test that a block size past a function's limit is refused static void test_block_size_limit(void) { @@ -214,6 +228,7 @@ int main(int argc, char **argv) RUN_TEST(test_response_flags); RUN_TEST(test_high_speed); RUN_TEST(test_low_speed); + RUN_TEST(test_low_speed_width); RUN_TEST(test_block_size_limit); RUN_TEST(test_byte_mode_limit);