Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/tinysd/sdio.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ struct tsd_sdio {

/** Fields from the card's common CIS. */
struct tsd_sdio_cis cis;

/** Number of I/O functions, not counting function 0. */
uint8_t functions;

/** Each function's largest block size from the CIS, or 0 when it has none. */
uint16_t max_block_size[8];
};

/**
Expand Down
118 changes: 75 additions & 43 deletions src/sdio_init.c
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#include <stdbool.h>
#include <stddef.h>
#include <string.h>

#include <tinysd/bits.h>
#include <tinysd/commands.h>
#include <tinysd/delay.h>

#include <tinysd/sdio.h>

// Bytes of the common CIS pointer
#define COMMON_CIS_POINTER_LEN 3
// Bytes of a CIS pointer
#define CIS_POINTER_LEN 3

// End of the CIS area, past which no tuple lies
#define CIS_AREA_END 0x18000u
Expand All @@ -19,17 +20,22 @@
// Tuple link that marks the last tuple of the chain
#define CIS_LINK_END 0xff

// Bytes of the manufacturer tuple read, two little-endian 16-bit ids
#define MANFID_TUPLE_LEN 4

// Function 0 extension tuple fields, from Table 16-6 of the SDIO specification
#define TPLFE_TYPE_FUNCTION_0 0x00
#define TPLFE_MAX_TRAN_SPEED_OFFSET 3
#define TPLFE_TYPE_FUNCTION_0 0x00
#define TPLFE_FN0_BLK_SIZE 1
#define TPLFE_MAX_TRAN_SPEED 3

// Function 1-7 extension tuple fields, from Table 16-8 of the SDIO specification
#define TPLFE_TYPE_FUNCTION 0x01
#define TPLFE_MAX_BLK_SIZE 12

// TPLFE_MAX_TRAN_SPEED fields, from Section 16.7.3 of the SDIO specification
#define TRAN_SPEED_UNIT TSD_BITS(2, 0)
#define TRAN_SPEED_TIME_VALUE TSD_BITS(6, 3)

// Bytes of a tuple's body read, enough to reach TPLFE_MAX_BLK_SIZE
#define TUPLE_BODY_LEN (TPLFE_MAX_BLK_SIZE + 2)

// Longest wait for the card to power up, in milliseconds
#define POWER_UP_TIMEOUT_MS 1000

Expand All @@ -51,8 +57,8 @@ static const uint32_t tran_speed_units_khz[8] = {100, 1000, 10000, 100000, 0, 0,
// Time values of TPLFE_MAX_TRAN_SPEED in tenths, with 0 for the reserved value
static const uint8_t tran_speed_time_values[16] = {0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80};

// Record the fields the driver uses from the common CIS at address, walking its tuples to the end of the chain
static int read_common_cis(struct tsd_sdio *card, uint32_t address)
// Walk a function's CIS and record the fields the driver uses
static int read_cis(struct tsd_sdio *card, uint8_t function, uint32_t address)
{
// Walk until the end of the chain or of the CIS area, leaving any field not found at 0
while (address < CIS_AREA_END) {
Expand All @@ -76,38 +82,37 @@ static int read_common_cis(struct tsd_sdio *card, uint32_t address)
if (ret < 0)
return ret;

// Take the ids from the manufacturer tuple, two little-endian 16-bit values
if (code == TSD_SDIO_CISTPL_MANFID && link >= MANFID_TUPLE_LEN) {
uint8_t bytes[MANFID_TUPLE_LEN];
for (int i = 0; i < MANFID_TUPLE_LEN; i++) {
ret = tsd_sdio_read_f0(card, address + TUPLE_HEADER_LEN + i, &bytes[i]);
// Read the start of the tuple's body, leaving bytes past its end at 0
uint8_t body[TUPLE_BODY_LEN] = {0};
if (code == TSD_SDIO_CISTPL_MANFID || code == TSD_SDIO_CISTPL_FUNCE) {
for (int i = 0; i < link && i < TUPLE_BODY_LEN; i++) {
ret = tsd_sdio_read_f0(card, address + TUPLE_HEADER_LEN + i, &body[i]);
if (ret < 0)
return ret;
}
card->cis.manufacturer_id = (uint16_t)(bytes[0] | (bytes[1] << 8));
card->cis.card_id = (uint16_t)(bytes[2] | (bytes[3] << 8));
}

// Take the fastest clock from function 0's extension tuple
if (code == TSD_SDIO_CISTPL_FUNCE && link > TPLFE_MAX_TRAN_SPEED_OFFSET) {
// Read the extension type and the transfer speed
uint8_t type;
uint8_t speed;
ret = tsd_sdio_read_f0(card, address + TUPLE_HEADER_LEN, &type);
if (ret < 0)
return ret;
ret = tsd_sdio_read_f0(card, address + TUPLE_HEADER_LEN + TPLFE_MAX_TRAN_SPEED_OFFSET, &speed);
if (ret < 0)
return ret;

// Decode the speed for function 0
if (type == TPLFE_TYPE_FUNCTION_0) {
uint32_t unit_khz = tran_speed_units_khz[TSD_FIELD_GET(TRAN_SPEED_UNIT, speed)];
uint32_t time_value = tran_speed_time_values[TSD_FIELD_GET(TRAN_SPEED_TIME_VALUE, speed)];
card->cis.max_clock_khz = unit_khz * time_value / 10;
}
// Take the ids from the manufacturer tuple in the common CIS
if (code == TSD_SDIO_CISTPL_MANFID && function == TSD_SDIO_COMMON_FUNCTION) {
card->cis.manufacturer_id = (uint16_t)(body[0] | (body[1] << 8));
card->cis.card_id = (uint16_t)(body[2] | (body[3] << 8));
}

// Take function 0's block size limit and fastest clock from its extension tuple
if (code == TSD_SDIO_CISTPL_FUNCE && function == TSD_SDIO_COMMON_FUNCTION && body[0] == TPLFE_TYPE_FUNCTION_0) {
card->max_block_size[0] = (uint16_t)(body[TPLFE_FN0_BLK_SIZE] | (body[TPLFE_FN0_BLK_SIZE + 1] << 8));

// Decode the speed
uint8_t speed = body[TPLFE_MAX_TRAN_SPEED];
uint32_t unit_khz = tran_speed_units_khz[TSD_FIELD_GET(TRAN_SPEED_UNIT, speed)];
uint32_t time_value = tran_speed_time_values[TSD_FIELD_GET(TRAN_SPEED_TIME_VALUE, speed)];
card->cis.max_clock_khz = unit_khz * time_value / 10;
}

// Take another function's block size limit from its extension tuple
if (code == TSD_SDIO_CISTPL_FUNCE && function != TSD_SDIO_COMMON_FUNCTION && body[0] == TPLFE_TYPE_FUNCTION)
card->max_block_size[function] = (uint16_t)(body[TPLFE_MAX_BLK_SIZE] | (body[TPLFE_MAX_BLK_SIZE + 1] << 8));

// Stop after a tuple marked last by its FFh link, once its body has been checked
if (link == CIS_LINK_END)
return 0;
Expand All @@ -133,8 +138,12 @@ static int power_up(struct tsd_sdio *card)
ret = tsd_host_send_command(card->host, TSD_CMD_IO_SEND_OP_COND, TSD_OCR_VOLTAGE_WINDOW, TSD_RESPONSE_R4, &ocr);
if (ret < 0)
return ret;
if (ocr & TSD_OCR_READY)

// Take the number of functions from the ready reply
if (ocr & TSD_OCR_READY) {
card->functions = (uint8_t)TSD_FIELD_GET(TSD_SDIO_R4_FUNCTIONS, ocr);
return 0;
}

// Wait before asking again
tsd_delay_us(POWER_UP_DELAY_US);
Expand All @@ -143,13 +152,18 @@ static int power_up(struct tsd_sdio *card)
return TSD_ERR_TIMEOUT;
}

// Read the address of the common CIS from its three-byte pointer in the CCCR
static int read_common_cis_pointer(struct tsd_sdio *card, uint32_t *address)
// Read the address of a function's CIS from its three-byte pointer in the CCCR or its FBR
static int read_cis_pointer(struct tsd_sdio *card, uint8_t function, uint32_t *address)
{
// Find the pointer, function 0's in the CCCR and the others' in their FBR
uint32_t pointer_address = TSD_SDIO_CCCR_COMMON_CIS_POINTER;
if (function)
pointer_address = TSD_SDIO_FBR_BASE(function) + TSD_SDIO_FBR_CIS_POINTER;

// Read the pointer's bytes
uint8_t pointer[COMMON_CIS_POINTER_LEN];
for (int i = 0; i < COMMON_CIS_POINTER_LEN; i++) {
int ret = tsd_sdio_read_f0(card, TSD_SDIO_CCCR_COMMON_CIS_POINTER + i, &pointer[i]);
uint8_t pointer[CIS_POINTER_LEN];
for (int i = 0; i < CIS_POINTER_LEN; i++) {
int ret = tsd_sdio_read_f0(card, pointer_address + i, &pointer[i]);
if (ret < 0)
return ret;
}
Expand All @@ -169,6 +183,8 @@ static int enumerate_card(struct tsd_sdio *card, struct tsd_host *host)
card->cis.manufacturer_id = 0;
card->cis.card_id = 0;
card->cis.max_clock_khz = 0;
card->functions = 0;
memset(card->max_block_size, 0, sizeof(card->max_block_size));

// Start at the enumeration clock on one data line, whatever the card was doing before
int ret = tsd_host_set_clock(host, ENUMERATION_CLOCK_KHZ);
Expand Down Expand Up @@ -202,10 +218,10 @@ static int enumerate_card(struct tsd_sdio *card, struct tsd_host *host)

// Read the fields the driver uses from the common CIS
uint32_t common_cis;
ret = read_common_cis_pointer(card, &common_cis);
ret = read_cis_pointer(card, TSD_SDIO_COMMON_FUNCTION, &common_cis);
if (ret < 0)
return ret;
return read_common_cis(card, common_cis);
return read_cis(card, TSD_SDIO_COMMON_FUNCTION, common_cis);
}

// Switch the card and then the host between 1-bit and 4-bit mode
Expand Down Expand Up @@ -297,5 +313,21 @@ int tsd_sdio_init(struct tsd_sdio *card, struct tsd_host *host, uint8_t width, u
if (clock_khz <= DEFAULT_SPEED_MAX_KHZ && card->cis.max_clock_khz && clock_khz > card->cis.max_clock_khz)
clock_khz = card->cis.max_clock_khz;

return tsd_host_set_clock(card->host, clock_khz);
// Run the clock
ret = tsd_host_set_clock(card->host, clock_khz);
if (ret < 0)
return ret;

// Read each function's block size limit from its CIS
for (uint8_t function = 1; function <= card->functions; function++) {
uint32_t cis;
ret = read_cis_pointer(card, function, &cis);
if (ret < 0)
return ret;
ret = read_cis(card, function, cis);
if (ret < 0)
return ret;
}

return 0;
}
13 changes: 13 additions & 0 deletions src/sdio_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ int tsd_sdio_set_block_size(struct tsd_sdio *card, uint8_t function, uint16_t bl
if (function > FUNCTION_MAX)
return TSD_ERR_PARAM;

// Refuse a size past the function's limit from the CIS
uint16_t limit = card->max_block_size[function];
if (limit && block_size > limit)
return TSD_ERR_PARAM;

// Find the size register, which function 0 keeps in the CCCR and the others in their FBR
uint32_t address = function ? TSD_SDIO_FBR_BASE(function) + TSD_SDIO_FBR_BLOCK_SIZE : TSD_SDIO_CCCR_FN0_BLOCK_SIZE;

Expand Down Expand Up @@ -237,9 +242,17 @@ int tsd_sdio_transfer(struct tsd_sdio *card, uint8_t function, uint32_t address,
chunk = blocks * block_size;
chunk_block_size = block_size;
} else {
// Move at most 512 bytes
chunk = remaining < BYTE_MODE_MAX ? remaining : BYTE_MODE_MAX;

// Move no more than a block
if (block_size && chunk > block_size)
chunk = block_size;

// Move no more than the function's limit from its CIS
uint16_t limit = card->max_block_size[function];
if (limit && chunk > limit)
chunk = limit;
}

// Run it at the address the transfer has reached, counting what it moved
Expand Down
49 changes: 44 additions & 5 deletions test/test_sdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,37 @@
// Where the tests put the common CIS in function 0
#define COMMON_CIS 0x1000

// Where the tests put function 1's CIS in function 0
#define FUNCTION_CIS 0x1100

// The card under test
static struct tsd_sdio card;

// Bytes the tests transfer, which the fake host leaves alone
static uint8_t buffer[1024];

// Put a common CIS in function 0 and point the CCCR at it
static void script_cis(const uint8_t *cis, size_t len)
static void fake_common_cis(const uint8_t *cis, size_t len)
{
memcpy(&host.f0[COMMON_CIS], cis, len);
host.f0[TSD_SDIO_CCCR_COMMON_CIS_POINTER] = (uint8_t)COMMON_CIS;
host.f0[TSD_SDIO_CCCR_COMMON_CIS_POINTER + 1] = (uint8_t)(COMMON_CIS >> 8);
}

// Give the card one function, with a CIS that limits it to 64-byte blocks
static void fake_function(void)
{
uint8_t cis[] = {
0x22, 0x0e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CISTPL_FUNCE for function 1
0x40, 0x00, // TPLFE_MAX_BLK_SIZE of 64
0xff, // CISTPL_END
};
memcpy(&host.f0[FUNCTION_CIS], cis, sizeof(cis));
host.f0[TSD_SDIO_FBR_BASE(1) + TSD_SDIO_FBR_CIS_POINTER] = (uint8_t)FUNCTION_CIS;
host.f0[TSD_SDIO_FBR_BASE(1) + TSD_SDIO_FBR_CIS_POINTER + 1] = (uint8_t)(FUNCTION_CIS >> 8);
host.replies[TSD_CMD_IO_SEND_OP_COND].response[0] |= TSD_FIELD_PREP(TSD_SDIO_R4_FUNCTIONS, 1);
}

// Bring the card up on one data line
static void init_card(void)
{
Expand All @@ -45,7 +62,7 @@ void setUp(void)

// Give it a common CIS that holds only the end tuple
uint8_t cis[] = {0xff};
script_cis(cis, sizeof(cis));
fake_common_cis(cis, sizeof(cis));
}

void tearDown(void)
Expand All @@ -60,7 +77,7 @@ static void test_manufacturer_from_cis(void)
0x20, 0x04, 0x89, 0x00, 0x34, 0x12, // CISTPL_MANFID, JEDEC code 89h as 0089h, card 1234h
0xff, // CISTPL_END
};
script_cis(cis, sizeof(cis));
fake_common_cis(cis, sizeof(cis));

init_card();
TEST_ASSERT_EQUAL_HEX16(0x0089, card.cis.manufacturer_id);
Expand All @@ -74,7 +91,7 @@ static void test_identify(void)
0x20, 0x04, 0x89, 0x00, 0x34, 0x12, // CISTPL_MANFID, JEDEC code 89h as 0089h, card 1234h
0xff, // CISTPL_END
};
script_cis(cis, sizeof(cis));
fake_common_cis(cis, sizeof(cis));

struct tsd_sdio_cis fields;
TEST_ASSERT_EQUAL(0, tsd_sdio_identify(&host.base, &fields));
Expand Down Expand Up @@ -158,13 +175,33 @@ static void test_low_speed(void)
0x22, 0x04, 0x00, 0x00, 0x02, 0x48, // CISTPL_FUNCE for function 0, 512-byte blocks at 400 kb/s
0xff, // CISTPL_END
};
script_cis(cis, sizeof(cis));
fake_common_cis(cis, sizeof(cis));

TEST_ASSERT_EQUAL(0, tsd_sdio_init(&card, &host.base, 1, 25000));
TEST_ASSERT_EQUAL_UINT16(512, card.max_block_size[0]);
TEST_ASSERT_EQUAL_UINT32(400, card.cis.max_clock_khz);
TEST_ASSERT_EQUAL_UINT32(400, host.clock_khz);
}

// Test that a block size past a function's limit is refused
static void test_block_size_limit(void)
{
fake_function();
init_card();

TEST_ASSERT_EQUAL(TSD_ERR_PARAM, tsd_sdio_set_block_size(&card, 1, 128));
}

// Test that a byte mode transfer moves no more than a function's limit at once
static void test_byte_mode_limit(void)
{
fake_function();
init_card();

TEST_ASSERT_EQUAL(0, tsd_sdio_transfer(&card, 1, 0, false, buffer, 128, 0, NULL));
TEST_ASSERT_EQUAL_UINT32(64, TSD_FIELD_GET(TSD_SDIO_ARG_COUNT, harness_find(TSD_CMD_IO_RW_EXTENDED)->argument));
}

int main(int argc, char **argv)
{
UNITY_BEGIN();
Expand All @@ -177,6 +214,8 @@ int main(int argc, char **argv)
RUN_TEST(test_response_flags);
RUN_TEST(test_high_speed);
RUN_TEST(test_low_speed);
RUN_TEST(test_block_size_limit);
RUN_TEST(test_byte_mode_limit);

return UNITY_END();
}
Loading