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:
Adrien Destugues
2019-02-11 21:36:25 +00:00
committed by waddlesplash
parent f167d21adc
commit cdc4a175ca
2 changed files with 220 additions and 89 deletions
+71 -55
View File
@@ -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;
+149 -34
View File
@@ -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*);