sdhci: refactor, get CMD0 working
- Define a class for each register, allowing easier access to relevant values. This avoids dealing with bitshifts and magic constants all over the code. - Fix definition of CMD0 and the flags used to submit it. We now get a command completion interrupt, yay! - Make some changes to support v3 and v4 controllers: - Enable PLL (harmless for older versions) - Manage faster and more configurable clock settings Change-Id: I8a97edcb881acc1ac2a8b0a2593930f18e777594 Reviewed-on: https://review.haiku-os.org/c/1029 Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
committed by
waddlesplash
parent
f167d21adc
commit
cdc4a175ca
@@ -59,33 +59,38 @@ static pci_x86_module_info* sPCIx86Module;
|
||||
static void
|
||||
sdhci_register_dump(uint8_t slot, struct registers* regs)
|
||||
{
|
||||
#ifdef TRACE_SDHCI
|
||||
TRACE("Register values for slot %d:\n", slot);
|
||||
TRACE("system_address: %d\n", regs->system_address);
|
||||
TRACE("%d blocks of size %d\n", regs->block_count, regs->block_size);
|
||||
TRACE("argument: %d\n", regs->argument);
|
||||
TRACE("transfer_mode: %d\n", regs->transfer_mode);
|
||||
TRACE("command: %d\n", regs->command);
|
||||
TRACE("command: %d\n", regs->command.Bits());
|
||||
TRACE("response:");
|
||||
for (int i = 0; i < 8; i++)
|
||||
TRACE(" %d", regs->response[i]);
|
||||
TRACE("\nbuffer_data_port: %d\n", regs->buffer_data_port);
|
||||
TRACE("present_state: %d\n", regs->present_state);
|
||||
TRACE("power_control: %d\n", regs->power_control);
|
||||
dprintf(" %d", regs->response[i]);
|
||||
dprintf("\n");
|
||||
TRACE("buffer_data_port: %d\n", regs->buffer_data_port);
|
||||
TRACE("present_state: %x\n", regs->present_state.Bits());
|
||||
TRACE("power_control: %d\n", regs->power_control.Bits());
|
||||
TRACE("host_control: %d\n", regs->host_control);
|
||||
TRACE("wakeup_control: %d\n", regs->wakeup_control);
|
||||
TRACE("block_gap_control: %d\n", regs->block_gap_control);
|
||||
TRACE("clock_control: %d\n", regs->clock_control);
|
||||
TRACE("software_reset: %d\n", regs->software_reset);
|
||||
TRACE("clock_control: %x\n", regs->clock_control.Bits());
|
||||
TRACE("software_reset: %d\n", regs->software_reset.Bits());
|
||||
TRACE("timeout_control: %d\n", regs->timeout_control);
|
||||
TRACE("interrupt_status: %x enable: %x signal: %x\n",
|
||||
regs->interrupt_status, regs->interrupt_status_enable,
|
||||
regs->interrupt_signal_enable);
|
||||
TRACE("auto_cmd12_error_status: %d\n", regs->auto_cmd12_error_status);
|
||||
TRACE("capabilities: %lld\n", regs->capabilities);
|
||||
TRACE("capabilities: %lld\n", regs->capabilities.Bits());
|
||||
TRACE("max_current_capabilities: %lld\n",
|
||||
regs->max_current_capabilities);
|
||||
TRACE("slot_interrupt_status: %d\n", regs->slot_interrupt_status);
|
||||
TRACE("host_controller_version %x\n", regs->host_controller_version);
|
||||
TRACE("host_controller_version spec %x vendor %x\n",
|
||||
regs->host_controller_version.specVersion,
|
||||
regs->host_controller_version.vendorVersion);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -93,73 +98,85 @@ static void
|
||||
sdhci_reset(struct registers* regs)
|
||||
{
|
||||
// if card is not present then no point of reseting the registers
|
||||
if (!(regs->present_state & SDHCI_CARD_DETECT))
|
||||
if (!regs->present_state.IsCardInserted())
|
||||
return;
|
||||
|
||||
// enabling software reset all
|
||||
regs->software_reset |= SDHCI_SOFTWARE_RESET_ALL;
|
||||
|
||||
// waiting for clock and power to get off
|
||||
while (regs->clock_control != 0 && regs->power_control != 0);
|
||||
regs->software_reset.ResetAll();
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
sdhci_set_clock(struct registers* regs, uint16_t base_clock_div)
|
||||
sdhci_set_clock(struct registers* regs)
|
||||
{
|
||||
uint32_t clock_control = regs->clock_control;
|
||||
int base_clock = SDHCI_BASE_CLOCK_FREQ(regs->capabilities);
|
||||
int base_clock = regs->capabilities.BaseClockFrequency();
|
||||
// Try to get as close to 400kHz as possible, but not faster
|
||||
int divider = base_clock * 1000 / 400;
|
||||
|
||||
TRACE("SDCLK frequency: %dMHz\n", base_clock);
|
||||
if (regs->host_controller_version.specVersion <= 1) {
|
||||
// Old controller only support power of two dividers up to 256,
|
||||
// round to next power of two up to 256
|
||||
if (divider > 256)
|
||||
divider = 256;
|
||||
|
||||
// clearing previous frequency
|
||||
clock_control &= SDHCI_CLR_FREQ_SEL;
|
||||
clock_control |= base_clock_div;
|
||||
divider--;
|
||||
divider |= divider >> 1;
|
||||
divider |= divider >> 2;
|
||||
divider |= divider >> 4;
|
||||
divider++;
|
||||
}
|
||||
|
||||
// enabling internal clock
|
||||
clock_control |= SDHCI_INTERNAL_CLOCK_ENABLE;
|
||||
regs->clock_control = clock_control;
|
||||
divider = regs->clock_control.SetDivider(divider);
|
||||
|
||||
// waiting till internal clock gets stable
|
||||
while (!(regs->clock_control & SDHCI_INTERNAL_CLOCK_STABLE));
|
||||
// Log the value after possible rounding by SetDivider (only even values
|
||||
// are allowed).
|
||||
TRACE("SDCLK frequency: %dMHz / %d = %dkHz\n", base_clock, divider,
|
||||
base_clock * 1000 / divider);
|
||||
|
||||
regs->clock_control |= SDHCI_SD_CLOCK_ENABLE; // enabling the SD clock
|
||||
// We have set the divider, now we can enable the internal clock.
|
||||
regs->clock_control.EnableInternal();
|
||||
|
||||
// wait until internal clock is stabilized
|
||||
while (!(regs->clock_control.InternalStable()));
|
||||
|
||||
regs->clock_control.EnablePLL();
|
||||
while (!(regs->clock_control.InternalStable()));
|
||||
|
||||
// Finally, route the clock to the SD card
|
||||
regs->clock_control.EnableSD();
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
sdhci_stop_clock(struct registers* regs)
|
||||
{
|
||||
regs->clock_control &= SDHCI_SD_CLOCK_DISABLE;
|
||||
regs->clock_control.DisableSD();
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
sdhci_set_power(struct registers* _regs)
|
||||
{
|
||||
uint16_t command = _regs->command;
|
||||
uint8_t supportedVoltages = _regs->capabilities.SupportedVoltages();
|
||||
if ((supportedVoltages & Capabilities::k3v3) != 0)
|
||||
_regs->power_control.SetVoltage(PowerControl::k3v3);
|
||||
else if ((supportedVoltages & Capabilities::k3v0) != 0)
|
||||
_regs->power_control.SetVoltage(PowerControl::k3v0);
|
||||
else if ((supportedVoltages & Capabilities::k1v8) != 0)
|
||||
_regs->power_control.SetVoltage(PowerControl::k1v8);
|
||||
else {
|
||||
_regs->power_control.PowerOff();
|
||||
ERROR("No voltage is supported\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDHCI_VOLTAGE_SUPPORTED(_regs->capabilities))
|
||||
if (SDHCI_VOLTAGE_SUPPORTED_33(_regs->capabilities))
|
||||
_regs->power_control |= SDHCI_VOLTAGE_SUPPORT_33;
|
||||
else if (SDHCI_VOLTAGE_SUPPORTED_30(_regs->capabilities))
|
||||
_regs->power_control |= SDHCI_VOLTAGE_SUPPORT_30;
|
||||
else
|
||||
_regs->power_control |= SDHCI_VOLTAGE_SUPPORT_18;
|
||||
else
|
||||
TRACE("No voltage is supported\n");
|
||||
|
||||
if (SDHCI_CARD_INSERTED(_regs->present_state) == 0) {
|
||||
if (!_regs->present_state.IsCardInserted()) {
|
||||
TRACE("Card not inserted\n");
|
||||
return;
|
||||
}
|
||||
|
||||
_regs->power_control |= SDHCI_BUS_POWER_ON;
|
||||
TRACE("Executed CMD0\n");
|
||||
|
||||
command = SDHCI_RESPONSE_R1 | SDHCI_CMD_CRC_EN
|
||||
| SDHCI_CMD_INDEX_EN | SDHCI_CMD_0;
|
||||
_regs->command |= command;
|
||||
TRACE("Execute CMD0\n");
|
||||
_regs->command.SendCommand(0, false);
|
||||
|
||||
DELAY(1000);
|
||||
}
|
||||
@@ -204,6 +221,8 @@ init_bus(device_node* node, void** bus_cookie)
|
||||
|| gDeviceManager->get_attr_uint8(node, BAR_INDEX, &bar, false) < B_OK)
|
||||
return -1;
|
||||
|
||||
TRACE("Controller has %d slots, first bar is %d\n", slot + 1, bar);
|
||||
|
||||
bus->node = node;
|
||||
bus->pci = pci;
|
||||
bus->device = device;
|
||||
@@ -272,9 +291,8 @@ init_bus(device_node* node, void** bus_cookie)
|
||||
| SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT;
|
||||
|
||||
sdhci_register_dump(slot, _regs);
|
||||
sdhci_set_clock(_regs, SDHCI_BASE_CLOCK_DIV_128);
|
||||
sdhci_set_clock(_regs);
|
||||
sdhci_set_power(_regs);
|
||||
sdhci_register_dump(slot, _regs);
|
||||
|
||||
*bus_cookie = bus;
|
||||
return status;
|
||||
@@ -287,10 +305,8 @@ sdhci_error_interrupt_recovery(struct registers* _regs)
|
||||
_regs->interrupt_signal_enable &= ~(SDHCI_INT_CMD_CMP
|
||||
| SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM);
|
||||
|
||||
if (_regs->interrupt_status & 7) {
|
||||
_regs->software_reset |= 1 << 1;
|
||||
while (_regs->command);
|
||||
}
|
||||
if (_regs->interrupt_status & 7)
|
||||
_regs->software_reset.ResetTransaction();
|
||||
|
||||
int16_t erorr_status = _regs->interrupt_status;
|
||||
_regs->interrupt_status &= ~(erorr_status);
|
||||
@@ -303,11 +319,11 @@ sdhci_generic_interrupt(void* data)
|
||||
TRACE("interrupt function called\n");
|
||||
sdhci_pci_mmc_bus_info* bus = (sdhci_pci_mmc_bus_info*)data;
|
||||
|
||||
uint16_t intmask, card_present;
|
||||
uint32_t intmask, card_present;
|
||||
|
||||
intmask = bus->_regs->slot_interrupt_status;
|
||||
|
||||
if (intmask == 0 || intmask == 0xffffffff) {
|
||||
if ((intmask == 0) || (intmask == 0xffffffff)) {
|
||||
TRACE("invalid command interrupt\n");
|
||||
|
||||
return B_UNHANDLED_INTERRUPT;
|
||||
@@ -335,8 +351,8 @@ sdhci_generic_interrupt(void* data)
|
||||
|
||||
// handling command interrupt
|
||||
if (intmask & SDHCI_INT_CMD_MASK) {
|
||||
TRACE("interrupt status error: %d\n", bus->_regs->interrupt_status);
|
||||
bus->_regs->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK);
|
||||
// TODO do something with the interrupt
|
||||
TRACE("Command interrupt handled\n");
|
||||
|
||||
return B_HANDLED_INTERRUPT;
|
||||
|
||||
@@ -20,37 +20,101 @@
|
||||
#define SDHCI_DEVICE_TYPE_ITEM "sdhci/type"
|
||||
#define SDHCI_BUS_TYPE_NAME "bus/sdhci/v1"
|
||||
|
||||
#define SDHCI_CARD_DETECT 1 << 16
|
||||
|
||||
#define SDHCI_SOFTWARE_RESET_ALL 1 << 0
|
||||
class Command {
|
||||
public:
|
||||
uint16_t Bits() { return fBits; }
|
||||
|
||||
#define SDHCI_BASE_CLOCK_FREQ(x) ((x >> 8) & 63)
|
||||
#define SDHCI_VOLTAGE_SUPPORTED(x) ((x >> 24) & 7)
|
||||
#define SDHCI_VOLTAGE_SUPPORTED_33(x) ((x >> 24) & 1)
|
||||
#define SDHCI_VOLTAGE_SUPPORTED_30(x) ((x >> 24) & 3)
|
||||
#define SDHCI_VOLTAGE_SUPPORT_33 7 << 1
|
||||
#define SDHCI_VOLTAGE_SUPPORT_30 6 << 1
|
||||
#define SDHCI_VOLTAGE_SUPPORT_18 5 << 1
|
||||
#define SDHCI_CARD_INSERTED(x) ((x >> 16) & 1)
|
||||
#define SDHCI_BASE_CLOCK_DIV_1 0 << 8
|
||||
#define SDHCI_BASE_CLOCK_DIV_2 1 << 8
|
||||
#define SDHCI_BASE_CLOCK_DIV_4 2 << 8
|
||||
#define SDHCI_BASE_CLOCK_DIV_8 4 << 8
|
||||
#define SDHCI_BASE_CLOCK_DIV_16 8 << 8
|
||||
#define SDHCI_BASE_CLOCK_DIV_32 16 << 8
|
||||
#define SDHCI_BASE_CLOCK_DIV_64 32 << 8
|
||||
#define SDHCI_BASE_CLOCK_DIV_128 64 << 8
|
||||
#define SDHCI_BASE_CLOCK_DIV_256 128 << 8
|
||||
#define SDHCI_INTERNAL_CLOCK_ENABLE 1 << 0
|
||||
#define SDHCI_INTERNAL_CLOCK_STABLE 1 << 1
|
||||
#define SDHCI_SD_CLOCK_ENABLE 1 << 2
|
||||
#define SDHCI_SD_CLOCK_DISABLE ~(1 << 2)
|
||||
#define SDHCI_CLR_FREQ_SEL ~(255 << 8)
|
||||
#define SDHCI_BUS_POWER_ON 1
|
||||
void SendCommand(uint8_t command, bool data)
|
||||
{
|
||||
fBits = (command << 8) | (data << 5);
|
||||
}
|
||||
|
||||
private:
|
||||
volatile uint16_t fBits;
|
||||
} __attribute__((packed));
|
||||
#define SDHCI_RESPONSE_R1 2
|
||||
#define SDHCI_CMD_CRC_EN 1 << 3
|
||||
#define SDHCI_CMD_INDEX_EN 1 << 4
|
||||
#define SDHCI_CMD_0 ~(63 << 8)
|
||||
|
||||
|
||||
class PresentState {
|
||||
public:
|
||||
uint32_t Bits() { return fBits; }
|
||||
|
||||
bool IsCardInserted() { return fBits & (1 << 16); }
|
||||
|
||||
private:
|
||||
volatile uint32_t fBits;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
class PowerControl {
|
||||
public:
|
||||
uint8_t Bits() { return fBits; }
|
||||
|
||||
void SetVoltage(int voltage) {
|
||||
fBits |= voltage | kBusPowerOn;
|
||||
}
|
||||
void PowerOff() { fBits &= ~kBusPowerOn; }
|
||||
|
||||
static const uint8_t k3v3 = 7 << 1;
|
||||
static const uint8_t k3v0 = 6 << 1;
|
||||
static const uint8_t k1v8 = 5 << 1;
|
||||
private:
|
||||
volatile uint8_t fBits;
|
||||
|
||||
static const uint8_t kBusPowerOn = 1;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
class ClockControl
|
||||
{
|
||||
public:
|
||||
uint16_t Bits() { return fBits; }
|
||||
|
||||
uint16_t SetDivider(uint16_t divider) {
|
||||
if (divider == 1)
|
||||
divider = 0;
|
||||
else
|
||||
divider /= 2;
|
||||
uint16_t bits = fBits & ~0xffc0;
|
||||
bits |= divider << 8;
|
||||
bits |= (divider >> 8) & 0xc0;
|
||||
fBits = bits;
|
||||
|
||||
return divider == 0 ? 1 : divider * 2;
|
||||
}
|
||||
|
||||
void EnableInternal() { fBits |= 1 << 0; }
|
||||
bool InternalStable() { return fBits & (1 << 1); }
|
||||
void EnableSD() { fBits |= 1 << 2; }
|
||||
void DisableSD() { fBits &= ~(1 << 2); }
|
||||
void EnablePLL() { fBits |= 1 << 3; }
|
||||
private:
|
||||
volatile uint16_t fBits;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
class SoftwareReset {
|
||||
public:
|
||||
uint8_t Bits() { return fBits; }
|
||||
|
||||
void ResetAll() {
|
||||
fBits |= 1;
|
||||
while(fBits & 1);
|
||||
}
|
||||
|
||||
void ResetTransaction() {
|
||||
fBits |= 2;
|
||||
while(fBits & 2);
|
||||
}
|
||||
|
||||
private:
|
||||
volatile uint8_t fBits;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
/* Interrupt registers */
|
||||
#define SDHCI_INT_CMD_CMP 0x00000001 // command complete enable
|
||||
#define SDHCI_INT_TRANS_CMP 0x00000002 // transfer complete enable
|
||||
@@ -68,40 +132,87 @@
|
||||
#define SDHCI_INT_CMD_MASK (SDHCI_INT_CMD_CMP | SDHCI_INT_CMD_ERROR_MASK)
|
||||
|
||||
|
||||
class Capabilities
|
||||
{
|
||||
public:
|
||||
uint64_t Bits() { return fBits; }
|
||||
|
||||
uint8_t SupportedVoltages() { return (fBits >> 24) & 7; }
|
||||
uint8_t BaseClockFrequency() { return (fBits >> 8) & 0xFF; }
|
||||
|
||||
static const uint8_t k3v3 = 1;
|
||||
static const uint8_t k3v0 = 2;
|
||||
static const uint8_t k1v8 = 4;
|
||||
|
||||
private:
|
||||
const uint64_t fBits;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
class HostControllerVersion {
|
||||
public:
|
||||
const uint8_t specVersion;
|
||||
const uint8_t vendorVersion;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
struct registers {
|
||||
// SD command generation
|
||||
volatile uint32_t system_address;
|
||||
volatile uint16_t block_size;
|
||||
volatile uint16_t block_count;
|
||||
volatile uint32_t argument;
|
||||
volatile uint16_t transfer_mode;
|
||||
volatile uint16_t command;
|
||||
Command command;
|
||||
|
||||
// Response
|
||||
volatile uint16_t response[8];
|
||||
|
||||
// Buffer Data Port
|
||||
volatile uint32_t buffer_data_port;
|
||||
volatile uint32_t present_state;
|
||||
|
||||
// Host control 1
|
||||
PresentState present_state;
|
||||
volatile uint8_t host_control;
|
||||
volatile uint8_t power_control;
|
||||
PowerControl power_control;
|
||||
volatile uint8_t block_gap_control;
|
||||
volatile uint8_t wakeup_control;
|
||||
volatile uint16_t clock_control;
|
||||
ClockControl clock_control;
|
||||
volatile uint8_t timeout_control;
|
||||
volatile uint8_t software_reset;
|
||||
SoftwareReset software_reset;
|
||||
|
||||
// Interrupt control
|
||||
volatile uint32_t interrupt_status;
|
||||
volatile uint32_t interrupt_status_enable;
|
||||
volatile uint32_t interrupt_signal_enable;
|
||||
volatile uint16_t auto_cmd12_error_status;
|
||||
|
||||
// Host control 2
|
||||
volatile uint16_t host_control_2;
|
||||
volatile uint64_t capabilities;
|
||||
|
||||
// Capabilities
|
||||
Capabilities capabilities;
|
||||
volatile uint64_t max_current_capabilities;
|
||||
|
||||
// Force event
|
||||
volatile uint16_t force_event_acmd_status;
|
||||
volatile uint16_t force_event_error_status;
|
||||
|
||||
// ADMA2
|
||||
volatile uint8_t adma_error_status;
|
||||
volatile uint8_t padding[3];
|
||||
volatile uint64_t adma_system_address;
|
||||
|
||||
// Preset values
|
||||
volatile uint64_t preset_value[2];
|
||||
volatile uint32_t :32;
|
||||
volatile uint16_t uhs2_preset_value;
|
||||
volatile uint16_t :16;
|
||||
|
||||
// ADMA3
|
||||
volatile uint64_t adma3_id_address;
|
||||
|
||||
// UHS-II
|
||||
volatile uint16_t uhs2_block_size;
|
||||
volatile uint16_t :16;
|
||||
volatile uint32_t uhs2_block_count;
|
||||
@@ -121,6 +232,8 @@ struct registers {
|
||||
volatile uint32_t uhs2_error_interrupt_status_enable;
|
||||
volatile uint32_t uhs2_error_interrupt_signal_enable;
|
||||
volatile uint8_t padding3[16];
|
||||
|
||||
// Pointers
|
||||
volatile uint16_t uhs2_settings_pointer;
|
||||
volatile uint16_t uhs2_host_capabilities_pointer;
|
||||
volatile uint16_t uhs2_test_pointer;
|
||||
@@ -128,8 +241,10 @@ struct registers {
|
||||
volatile uint16_t vendor_specific_pointer;
|
||||
volatile uint16_t reserved_specific_pointer;
|
||||
volatile uint8_t padding4[16];
|
||||
|
||||
// Common area
|
||||
volatile uint16_t slot_interrupt_status;
|
||||
volatile uint16_t host_controller_version;
|
||||
HostControllerVersion host_controller_version;
|
||||
} __attribute__((packed));
|
||||
|
||||
typedef void* sdhci_mmc_bus;
|
||||
@@ -142,7 +257,7 @@ typedef void* sdhci_mmc_bus;
|
||||
|
||||
static void sdhci_register_dump(uint8_t, struct registers*);
|
||||
static void sdhci_reset(struct registers*);
|
||||
static void sdhci_set_clock(struct registers*, uint16_t);
|
||||
static void sdhci_set_clock(struct registers*);
|
||||
static void sdhci_set_power(struct registers*);
|
||||
static void sdhci_stop_clock(struct registers*);
|
||||
void sdhci_error_interrupt_recovery(struct registers*);
|
||||
|
||||
Reference in New Issue
Block a user