#include <algorithm>
#include <new>
#include <stdio.h>
#include <string.h>
#include <bus/PCI.h>
#include <ACPI.h>
#include "acpi.h"
#include <KernelExport.h>
#include "IOSchedulerSimple.h"
#include "mmc.h"
#include "sdhci.h"
#define TRACE_SDHCI
#ifdef TRACE_SDHCI
# define TRACE(x...) dprintf("\33[33msdhci:\33[0m " x)
#else
# define TRACE(x...) ;
#endif
#define TRACE_ALWAYS(x...) dprintf("\33[33msdhci:\33[0m " x)
#define ERROR(x...) dprintf("\33[33msdhci:\33[0m " x)
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
#define SDHCI_DEVICE_MODULE_NAME "busses/mmc/sdhci/driver_v1"
device_manager_info* gDeviceManager;
device_module_info* gMMCBusController;
static int32
sdhci_generic_interrupt(void* data)
{
SdhciBus* bus = (SdhciBus*)data;
return bus->HandleInterrupt();
}
SdhciBus::SdhciBus(struct registers* registers, uint8_t irq, bool poll)
:
fRegisters(registers),
fIrq(irq)
{
if (irq == 0 || irq == 0xff) {
ERROR("IRQ not assigned\n");
fStatus = B_BAD_DATA;
return;
}
fInterruptNotifier.Init(this, "SDHCI interrupts");
DisableInterrupts();
fStatus = install_io_interrupt_handler(fIrq,
sdhci_generic_interrupt, this, 0);
if (fStatus != B_OK) {
ERROR("can't install interrupt handler\n");
return;
}
Reset();
TRACE("Controller spec version: %d, vendor version: %#02x\n",
fRegisters->host_controller_version.specVersion,
fRegisters->host_controller_version.vendorVersion);
TRACE("Capabilities: %s%s%s%s%s%s%s%s%s%s%s%s%s%s\n"
" Clock multiplier: %" PRIx8 "\n"
" Retuning modes: %" PRIx8 "\n"
" Retuning timer count: %" PRIx8 "\n"
" Slot type: %" PRIx8 "\n"
" Supported voltages: %" PRIx8 "\n"
" Max block length: %" PRIx8 "\n"
" Base clock frequency: %" PRId8 " MHz\n"
" Timeout clock: %" PRId8 " kHz\n",
fRegisters->capabilities.UseTuningForSDR50() ? "SDR50 needs retuning, " : "",
fRegisters->capabilities.TypeDSupport() ? "Type-D, " : "",
fRegisters->capabilities.TypeCSupport() ? "Type-C, " : "",
fRegisters->capabilities.TypeASupport() ? "Type-A, " : "",
fRegisters->capabilities.DDR50Support() ? "DDR50, " : "",
fRegisters->capabilities.SDR104Support() ? "SDR104, " : "",
fRegisters->capabilities.SDR50Support() ? "SDR50, " : "",
fRegisters->capabilities.AsynchronousInterrupts() ? "Asynchronous interrupts, " : "",
fRegisters->capabilities.SystemBus64Bits() ? "64-bit system bus, " : "",
fRegisters->capabilities.SuspendResume() ? "Suspend/Resume, " : "",
fRegisters->capabilities.SimpleDMA() ? "Simple DMA, " : "",
fRegisters->capabilities.HighSpeed() ? "High speed, " : "",
fRegisters->capabilities.AdvancedDMA() ? "Advanced DMA, " : "",
fRegisters->capabilities.Embedded8Bit() ? "8-bit Embedded mode, " : "",
fRegisters->capabilities.ClockMultiplier(),
fRegisters->capabilities.RetuningModes(),
fRegisters->capabilities.RetuningTimerCount(),
fRegisters->capabilities.SlotType(),
fRegisters->capabilities.SupportedVoltages(),
fRegisters->capabilities.MaxBlockLength(),
fRegisters->capabilities.BaseClockFrequency(),
fRegisters->capabilities.TimeoutClockFrequency());
TRACE("Initial host control: %x\n", fRegisters->host_control.Bits());
TRACE("Initial host control 2: %x\n", fRegisters->host_control_2);
if (fRegisters->host_controller_version.specVersion > 3) {
fRegisters->host_control_2 &= ~(1<<12);
TRACE("Host control 2 after disabling v4 DMA mode: %x\n", fRegisters->host_control_2);
}
if (PowerOn()) {
SetClock(400, false);
}
fRegisters->timeout_control.SetDivider(fRegisters->capabilities.TimeoutClockFrequency(), 500);
EnableInterrupts(SDHCI_INT_CMD_CMP | SDHCI_INT_CARD_REM
| SDHCI_INT_TRANS_CMP | SDHCI_INT_COMMAND_TIMEOUT);
fRegisters->interrupt_status_enable |= SDHCI_INT_ERROR_MASK | SDHCI_INT_NORMAL_MASK;
if (poll) {
fWorkerThread = spawn_kernel_thread(_WorkerThread, "SD bus poller",
B_NORMAL_PRIORITY, this);
resume_thread(fWorkerThread);
}
}
SdhciBus::~SdhciBus()
{
DisableInterrupts();
if (fIrq != 0)
remove_io_interrupt_handler(fIrq, sdhci_generic_interrupt, this);
area_id regs_area = area_for(fRegisters);
delete_area(regs_area);
fStatus = B_SHUTTING_DOWN;
status_t result;
if (fWorkerThread != 0)
wait_for_thread(fWorkerThread, &result);
}
void
SdhciBus::EnableInterrupts(uint32_t mask)
{
fRegisters->interrupt_status_enable |= mask;
fRegisters->interrupt_signal_enable |= mask;
}
void
SdhciBus::DisableInterrupts()
{
fRegisters->interrupt_status_enable = 0;
fRegisters->interrupt_signal_enable = 0;
}
status_t
SdhciBus::ExecuteCommand(uint8_t command, uint32_t argument, uint32_t* response)
{
TRACE("ExecuteCommand(%d, %x)\n", command, argument);
fCommandResult = 0;
if (fRegisters->present_state.CommandInhibit()) {
TRACE_ALWAYS("Command execution impossible, command inhibit\n");
return B_BUSY;
}
if (fRegisters->present_state.DataInhibit()) {
TRACE_ALWAYS("Command execution unwise, data inhibit\n");
return B_BUSY;
}
ConditionVariableEntry waiter;
fInterruptNotifier.Add(&waiter);
uint32_t replyType;
uint16 transferMode = 0;
switch (command) {
case SD_GO_IDLE_STATE:
replyType = Command::kNoReplyType;
break;
case SD_ALL_SEND_CID:
case SD_SEND_CSD:
replyType = Command::kR2Type;
break;
case SD_SEND_RELATIVE_ADDR:
replyType = Command::kR6Type;
break;
case SD_SELECT_DESELECT_CARD:
case SD_ERASE:
replyType = Command::kR1bType;
break;
case SD_SEND_IF_COND:
replyType = Command::kR7Type;
break;
case SD_READ_SINGLE_BLOCK:
transferMode = TransferMode::kRead | TransferMode::kDmaEnable;
replyType = Command::kR1Type | Command::kDataPresent;
break;
case SD_READ_MULTIPLE_BLOCKS:
transferMode = TransferMode::kRead | TransferMode::kMulti
| TransferMode::kAutoCmd12Enable | TransferMode::kBlockCountEnable
| TransferMode::kDmaEnable;
replyType = Command::kR1Type | Command::kDataPresent;
break;
case SD_WRITE_SINGLE_BLOCK:
transferMode = TransferMode::kWrite | TransferMode::kDmaEnable;
replyType = Command::kR1Type | Command::kDataPresent;
break;
case SD_WRITE_MULTIPLE_BLOCKS:
transferMode = TransferMode::kWrite | TransferMode::kMulti
| TransferMode::kAutoCmd12Enable | TransferMode::kBlockCountEnable
| TransferMode::kDmaEnable;
replyType = Command::kR1Type | Command::kDataPresent;
break;
case SD_APP_CMD:
case SD_ERASE_WR_BLK_START:
case SD_ERASE_WR_BLK_END:
case SD_SET_BUS_WIDTH:
replyType = Command::kR1Type;
break;
case SD_SEND_OP_COND:
replyType = Command::kR3Type;
break;
default:
ERROR("Unknown command %x\n", command);
return B_BAD_DATA;
}
if ((replyType & Command::k32BitResponseCheckBusy) != 0
&& command != SD_STOP_TRANSMISSION && command != SD_IO_ABORT) {
if (fRegisters->present_state.DataInhibit()) {
ERROR("Execution aborted, data inhibit\n");
return B_BUSY;
}
}
if (fRegisters->present_state.CommandInhibit())
panic("Command line busy at start of execute command\n");
fRegisters->argument = argument;
if ((replyType == Command::kR1bType)
|| (replyType == (Command::kR1Type | Command::kDataPresent)))
fRegisters->transfer_mode = transferMode;
fRegisters->command.SendCommand(command, replyType);
TRACE("Wait for command complete...");
do {
status_t result = waiter.Wait(B_RELATIVE_TIMEOUT, 1000000);
if (result == B_TIMED_OUT) {
TRACE("Command complete interrupt did not trigger for a while, status %x\n",
fRegisters->interrupt_status);
} else if (result != B_OK)
panic("sdhci: Failed to wait for command complete: %s", strerror(result));
fInterruptNotifier.Add(&waiter);
TRACE("Command status: %x\n", fCommandResult);
TRACE("real status = %x command line busy: %d\n",
fRegisters->interrupt_status,
fRegisters->present_state.CommandInhibit());
} while (fCommandResult == 0);
TRACE("Command response available\n");
if (fCommandResult & SDHCI_INT_ERROR) {
fRegisters->interrupt_status |= fCommandResult;
if (fCommandResult & SDHCI_INT_COMMAND_TIMEOUT) {
ERROR("Command execution timed out\n");
if (fRegisters->present_state.CommandInhibit()) {
TRACE("Command line is still busy, clearing it\n");
fRegisters->software_reset.ResetCommandLine();
}
return B_TIMED_OUT;
}
if (fCommandResult & SDHCI_INT_COMMAND_CRC) {
ERROR("CRC error\n");
return B_BAD_VALUE;
}
ERROR("Command execution failed %x\n", fCommandResult);
return B_ERROR;
}
if (fRegisters->present_state.CommandInhibit()) {
TRACE("Command execution failed, card stalled\n");
fRegisters->software_reset.ResetCommandLine();
return B_ERROR;
}
switch (replyType & Command::kReplySizeMask) {
case Command::k32BitResponse:
*response = fRegisters->response[0];
break;
case Command::k128BitResponse:
response[0] = fRegisters->response[0];
response[1] = fRegisters->response[1];
response[2] = fRegisters->response[2];
response[3] = fRegisters->response[3];
break;
default:
break;
}
if ((replyType == Command::kR1bType)
&& (fCommandResult & SDHCI_INT_TRANS_CMP) == 0) {
TRACE("Waiting for data line...\n");
fInterruptNotifier.Add(&waiter);
while (fRegisters->present_state.DataInhibit()) {
status_t result = waiter.Wait();
if (result != B_OK)
panic("sdhci: Failed to wait for data line release: %s", strerror(result));
fInterruptNotifier.Add(&waiter);
}
TRACE("Dataline is released.\n");
}
ERROR("Command execution %d complete\n", command);
return B_OK;
}
status_t
SdhciBus::InitCheck()
{
return fStatus;
}
void
SdhciBus::Reset()
{
if (!fRegisters->software_reset.ResetAll())
ERROR("SdhciBus::Reset: SoftwareReset timeout\n");
}
void
SdhciBus::SetClock(int kilohertz, bool allowAuto)
{
if (allowAuto && (fRegisters->host_controller_version.specVersion > 2)) {
TRACE("Ignoring set_clock, controller support presets\n");
fRegisters->host_control_2 |= (1<<15);
TRACE("Host control 2 after enabling preset mode: %x\n", fRegisters->host_control_2);
return;
}
int base_clock = fRegisters->capabilities.BaseClockFrequency();
int divider = base_clock * 1000 / kilohertz;
if (fRegisters->host_controller_version.specVersion <= 1) {
if (divider > 256)
divider = 256;
divider--;
divider |= divider >> 1;
divider |= divider >> 2;
divider |= divider >> 4;
divider++;
}
divider = fRegisters->clock_control.SetDivider(divider);
TRACE("SDCLK frequency: requested %dkHz, effective %dMHz / %d = %dkHz\n", kilohertz,
base_clock, divider, base_clock * 1000 / divider);
fRegisters->clock_control.EnableInternal();
while (!(fRegisters->clock_control.InternalStable()));
fRegisters->clock_control.EnablePLL();
while (!(fRegisters->clock_control.InternalStable()));
fRegisters->clock_control.EnableSD();
}
status_t
SdhciBus::DoIO(uint8_t command, IOOperation* operation, bool offsetAsSectors)
{
bool isWrite = operation->IsWrite();
static const uint32 kBlockSize = 512;
off_t offset = operation->Offset();
generic_size_t length = operation->Length();
TRACE("%s %" B_PRIuGENADDR " bytes at %" B_PRIdOFF "\n",
isWrite ? "Write" : "Read", length, offset);
ASSERT(offset % kBlockSize == 0);
ASSERT(length % kBlockSize == 0);
const generic_io_vec* vecs = operation->Vecs();
generic_size_t vecOffset = 0;
status_t result = B_OK;
while (length > 0) {
size_t toCopy = std::min((generic_size_t)length,
vecs->length - vecOffset);
if (toCopy == 0) {
vecs++;
vecOffset = 0;
continue;
}
ASSERT(toCopy % kBlockSize == 0);
fRegisters->system_address = vecs->base + vecOffset;
fRegisters->block_size.ConfigureTransfer(kBlockSize,
BlockSize::kDmaBoundary512K);
fRegisters->block_count = toCopy / kBlockSize;
ConditionVariableEntry waiter;
fInterruptNotifier.Add(&waiter);
uint32_t response;
result = ExecuteCommand(command,
offset / (offsetAsSectors ? kBlockSize : 1), &response);
if (result != B_OK)
break;
TRACE("Wait for transfer complete...");
while ((fCommandResult & SDHCI_INT_TRANS_CMP) == 0) {
status_t result = waiter.Wait(B_RELATIVE_TIMEOUT, 1000000);
if (result == B_TIMED_OUT) {
TRACE("Transfer complete interrupt did not trigger for a while, status %x\n",
fRegisters->interrupt_status);
} else if (result != B_OK)
panic("sdhci: Failed to wait for end of DMA transfer: %s", strerror(result));
fInterruptNotifier.Add(&waiter);
}
TRACE("transfer complete OK.\n");
length -= toCopy;
vecOffset += toCopy;
offset += toCopy;
}
return result;
}
void
SdhciBus::SetScanSemaphore(sem_id sem)
{
fScanSemaphore = sem;
if (fRegisters->present_state.IsCardInserted())
release_sem(fScanSemaphore);
EnableInterrupts(SDHCI_INT_CARD_INS);
}
void
SdhciBus::SetBusWidth(int width)
{
uint8_t widthBits;
switch(width) {
case 1:
widthBits = HostControl::kDataTransfer1Bit;
break;
case 4:
widthBits = HostControl::kDataTransfer4Bit;
break;
case 8:
widthBits = HostControl::kDataTransfer8Bit;
break;
default:
panic("Incorrect bitwidth value");
return;
}
fRegisters->host_control.SetDataTransferWidth(widthBits);
}
bool
SdhciBus::PowerOn()
{
if (!fRegisters->present_state.IsCardInserted()) {
TRACE("Card not inserted, not powering on for now\n");
return false;
}
uint8_t supportedVoltages = fRegisters->capabilities.SupportedVoltages();
if ((supportedVoltages & Capabilities::k3v3) != 0)
fRegisters->power_control.SetVoltage(PowerControl::k3v3);
else if ((supportedVoltages & Capabilities::k3v0) != 0)
fRegisters->power_control.SetVoltage(PowerControl::k3v0);
else if ((supportedVoltages & Capabilities::k1v8) != 0)
fRegisters->power_control.SetVoltage(PowerControl::k1v8);
else {
fRegisters->power_control.PowerOff();
ERROR("No voltage is supported\n");
return false;
}
return true;
}
void
SdhciBus::RecoverError()
{
fRegisters->interrupt_signal_enable &= ~(SDHCI_INT_CMD_CMP
| SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM);
if (fRegisters->interrupt_status & 7)
fRegisters->software_reset.ResetCommandLine();
int16_t error_status = fRegisters->interrupt_status;
fRegisters->interrupt_status &= ~(error_status);
}
int32
SdhciBus::HandleInterrupt()
{
#if 0
if ( !(fRegisters->slot_interrupt_status & (1 << fSlot)) ) {
TRACE("interrupt not for me.\n");
return B_UNHANDLED_INTERRUPT;
}
#endif
uint32_t intmask = fRegisters->interrupt_status;
if ((intmask == 0) || (intmask == 0xffffffff)) {
return B_UNHANDLED_INTERRUPT;
}
TRACE("interrupt function called %x\n", intmask);
if ((intmask & SDHCI_INT_CARD_REM) != 0) {
if (!fRegisters->present_state.IsCardInserted())
fRegisters->power_control.PowerOff();
else
TRACE("Card removed interrupt, but card is inserted\n");
fRegisters->interrupt_status |= SDHCI_INT_CARD_REM;
TRACE("Card removal interrupt handled\n");
}
if ((intmask & SDHCI_INT_CARD_INS) != 0) {
if (fRegisters->present_state.IsCardInserted()) {
if (PowerOn())
SetClock(400, false);
release_sem_etc(fScanSemaphore, 1, B_DO_NOT_RESCHEDULE);
} else
TRACE("Card insertion interrupt, but card is removed\n");
fRegisters->interrupt_status |= SDHCI_INT_CARD_INS;
TRACE("Card presence interrupt handled\n");
}
if (intmask & SDHCI_INT_CMD_MASK) {
fCommandResult |= intmask;
fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK);
fInterruptNotifier.NotifyAll();
TRACE("Command complete interrupt handled\n");
}
if (intmask & SDHCI_INT_TRANS_CMP) {
fCommandResult |= intmask;
fRegisters->interrupt_status |= SDHCI_INT_TRANS_CMP;
fInterruptNotifier.NotifyAll();
TRACE("Transfer complete interrupt handled\n");
}
if (intmask & SDHCI_INT_BUS_POWER) {
fRegisters->interrupt_status |= SDHCI_INT_BUS_POWER;
TRACE("card is consuming too much power\n");
}
intmask = fRegisters->interrupt_status;
if (intmask != 0) {
ERROR("Remaining interrupts at end of handler: %x\n", intmask);
}
return B_HANDLED_INTERRUPT;
}
status_t
SdhciBus::_WorkerThread(void* cookie) {
SdhciBus* bus = (SdhciBus*)cookie;
while (bus->fStatus != B_SHUTTING_DOWN) {
uint32_t intmask = bus->fRegisters->interrupt_status;
if (intmask & SDHCI_INT_CMD_CMP) {
bus->fCommandResult = intmask;
bus->fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK);
bus->fInterruptNotifier.NotifyAll();
}
if (intmask & SDHCI_INT_TRANS_CMP) {
bus->fCommandResult = intmask;
bus->fRegisters->interrupt_status |= SDHCI_INT_TRANS_CMP;
bus->fInterruptNotifier.NotifyAll();
}
snooze(100);
}
TRACE("poller thread terminating");
return B_OK;
}
void
uninit_bus(void* bus_cookie)
{
SdhciBus* bus = (SdhciBus*)bus_cookie;
delete bus;
}
void
bus_removed(void* bus_cookie)
{
return;
}
static status_t
register_child_devices(void* cookie)
{
CALLED();
SdhciDevice* context = (SdhciDevice*)cookie;
status_t status = B_OK;
const char* bus;
device_node* parent = gDeviceManager->get_parent_node(context->fNode);
status = gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false);
if (status != B_OK) {
TRACE("Could not find required attribute device/bus\n");
return status;
}
if (strcmp(bus, "pci") == 0)
status = register_child_devices_pci(cookie);
else if (strcmp(bus, "acpi") == 0)
status = register_child_devices_acpi(cookie);
else
status = B_BAD_VALUE;
return status;
}
static status_t
init_device(device_node* node, void** device_cookie)
{
CALLED();
SdhciDevice* context = new(std::nothrow)SdhciDevice;
if (context == NULL)
return B_NO_MEMORY;
context->fNode = node;
*device_cookie = context;
status_t status = B_OK;
const char* bus;
device_node* parent = gDeviceManager->get_parent_node(node);
status = gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false);
if (status != B_OK) {
TRACE("Could not find required attribute device/bus\n");
return status;
}
if (strcmp(bus, "pci") == 0)
return init_device_pci(node, context);
return B_OK;
}
static void
uninit_device(void* device_cookie)
{
SdhciDevice* context = (SdhciDevice*)device_cookie;
device_node* parent = gDeviceManager->get_parent_node(context->fNode);
const char* bus;
if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK) {
TRACE("Could not find required attribute device/bus\n");
}
if (strcmp(bus, "pci") == 0)
uninit_device_pci(context, parent);
gDeviceManager->put_node(parent);
delete context;
}
static status_t
register_device(device_node* parent)
{
device_attr attrs[] = {
{B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "SD Host Controller"}},
{}
};
return gDeviceManager->register_node(parent, SDHCI_DEVICE_MODULE_NAME,
attrs, NULL, NULL);
}
static float
supports_device(device_node* parent)
{
const char* bus;
if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)
!= B_OK) {
TRACE("Could not find required attribute device/bus\n");
return -1;
}
if (strcmp(bus, "pci") == 0)
return supports_device_pci(parent);
else if (strcmp(bus, "acpi") == 0)
return supports_device_acpi(parent);
return 0.0f;
}
module_dependency module_dependencies[] = {
{ MMC_BUS_MODULE_NAME, (module_info**)&gMMCBusController},
{ B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager },
{}
};
status_t
set_clock(void* controller, uint32_t kilohertz)
{
SdhciBus* bus = (SdhciBus*)controller;
bus->SetClock(kilohertz, true);
return B_OK;
}
status_t
execute_command(void* controller, uint8_t command, uint32_t argument,
uint32_t* response)
{
SdhciBus* bus = (SdhciBus*)controller;
return bus->ExecuteCommand(command, argument, response);
}
status_t
do_io(void* controller, uint8_t command, IOOperation* operation,
bool offsetAsSectors)
{
SdhciBus* bus = (SdhciBus*)controller;
return bus->DoIO(command, operation, offsetAsSectors);
}
void
set_scan_semaphore(void* controller, sem_id sem)
{
SdhciBus* bus = (SdhciBus*)controller;
return bus->SetScanSemaphore(sem);
}
void
set_bus_width(void* controller, int width)
{
SdhciBus* bus = (SdhciBus*)controller;
return bus->SetBusWidth(width);
}
static driver_module_info sSDHCIDevice = {
{
SDHCI_DEVICE_MODULE_NAME,
0,
NULL
},
supports_device,
register_device,
init_device,
uninit_device,
register_child_devices,
NULL,
NULL,
};
module_info* modules[] = {
(module_info* )&sSDHCIDevice,
(module_info* )&gSDHCIPCIDeviceModule,
(module_info* )&gSDHCIACPIDeviceModule,
NULL
};