sdhci: add ACPI-enumerated device support
Change-Id: I5c42344b2499bd22581d6b564192decbce937645 Reviewed-on: https://review.haiku-os.org/c/haiku/+/7456 Reviewed-by: Adrien Destugues <[email protected]> Tested-by: Commit checker robot <[email protected]>
This commit is contained in:
committed by
Adrien Destugues
parent
b140858b8c
commit
1cdb3beb26
@@ -29,7 +29,7 @@ AddFilesToPackage add-ons kernel busses ata
|
|||||||
: generic_ide_pci it8211 legacy_sata silicon_image_3112 highpoint_ide_pci ide_isa@x86 ;
|
: generic_ide_pci it8211 legacy_sata silicon_image_3112 highpoint_ide_pci ide_isa@x86 ;
|
||||||
|
|
||||||
AddFilesToPackage add-ons kernel busses i2c : pch_i2c@x86,x86_64 ;
|
AddFilesToPackage add-ons kernel busses i2c : pch_i2c@x86,x86_64 ;
|
||||||
AddFilesToPackage add-ons kernel busses mmc : sdhci_pci ;
|
AddFilesToPackage add-ons kernel busses mmc : sdhci ;
|
||||||
AddFilesToPackage add-ons kernel busses pci :
|
AddFilesToPackage add-ons kernel busses pci :
|
||||||
<pci>designware@riscv64 <pci>ecam@riscv64,arm,arm64 <pci>x86@x86,x86_64 ;
|
<pci>designware@riscv64 <pci>ecam@riscv64,arm,arm64 <pci>x86@x86,x86_64 ;
|
||||||
AddFilesToPackage add-ons kernel busses random : ccp_rng@x86,x86_64 virtio_rng ;
|
AddFilesToPackage add-ons kernel busses random : ccp_rng@x86,x86_64 virtio_rng ;
|
||||||
@@ -202,7 +202,7 @@ AddBootModuleSymlinksToPackage
|
|||||||
<pci>designware@riscv64 <pci>ecam@riscv64,arm,arm64 <pci>x86@x86,x86_64
|
<pci>designware@riscv64 <pci>ecam@riscv64,arm,arm64 <pci>x86@x86,x86_64
|
||||||
fdt@riscv64,arm,arm64
|
fdt@riscv64,arm,arm64
|
||||||
scsi scsi_cd scsi_disk scsi_periph silicon_image_3112 highpoint_ide_pci
|
scsi scsi_cd scsi_disk scsi_periph silicon_image_3112 highpoint_ide_pci
|
||||||
sdhci_pci
|
sdhci
|
||||||
usb usb_disk <usb>ehci <usb>ohci <usb>uhci <usb>xhci
|
usb usb_disk <usb>ehci <usb>ohci <usb>uhci <usb>xhci
|
||||||
virtio virtio_block virtio_mmio@riscv64,arm,arm64 virtio_pci virtio_scsi
|
virtio virtio_block virtio_mmio@riscv64,arm,arm64 virtio_pci virtio_scsi
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -261,7 +261,9 @@ MMCBus::_WorkerThread(void* cookie)
|
|||||||
// (and a matching published device on our side).
|
// (and a matching published device on our side).
|
||||||
uint32_t cid[4];
|
uint32_t cid[4];
|
||||||
|
|
||||||
while (bus->ExecuteCommand(0, SD_ALL_SEND_CID, 0, cid) == B_OK) {
|
// This being an if statement as opposed to a while statement restricts
|
||||||
|
// it to one device per bus.
|
||||||
|
if (bus->ExecuteCommand(0, SD_ALL_SEND_CID, 0, cid) == B_OK) {
|
||||||
bus->ExecuteCommand(0, SD_SEND_RELATIVE_ADDR, 0, &response);
|
bus->ExecuteCommand(0, SD_SEND_RELATIVE_ADDR, 0, &response);
|
||||||
|
|
||||||
TRACE("RCA: %x Status: %x\n", response >> 16, response & 0xFFFF);
|
TRACE("RCA: %x Status: %x\n", response >> 16, response & 0xFFFF);
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ SubDirC++Flags -fno-rtti ;
|
|||||||
UsePrivateKernelHeaders ;
|
UsePrivateKernelHeaders ;
|
||||||
UsePrivateHeaders drivers ;
|
UsePrivateHeaders drivers ;
|
||||||
SubDirHdrs $(HAIKU_TOP) src system kernel device_manager ;
|
SubDirHdrs $(HAIKU_TOP) src system kernel device_manager ;
|
||||||
|
SubDirHdrs $(HAIKU_TOP) src add-ons kernel bus_managers acpi acpica include ;
|
||||||
|
SubDirHdrs $(HAIKU_TOP) src add-ons kernel bus_managers acpi acpica include platform ;
|
||||||
|
|
||||||
KernelAddon sdhci_pci :
|
KernelAddon sdhci :
|
||||||
|
sdhci.cpp
|
||||||
|
sdhci_acpi.cpp
|
||||||
sdhci_pci.cpp
|
sdhci_pci.cpp
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -0,0 +1,833 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2024 Haiku, Inc. All rights reserved.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* B Krishnan Iyer, [email protected]
|
||||||
|
* Adrien Destugues, [email protected]
|
||||||
|
* Ron Ben Aroya, [email protected]
|
||||||
|
*/
|
||||||
|
#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"
|
||||||
|
#define SDHCI_ACPI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci/acpi/device/v1"
|
||||||
|
#define SDHCI_PCI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci/pci/device/v1"
|
||||||
|
|
||||||
|
#define SLOT_NUMBER "device/slot"
|
||||||
|
#define BAR_INDEX "device/bar"
|
||||||
|
|
||||||
|
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),
|
||||||
|
fSemaphore(0)
|
||||||
|
{
|
||||||
|
if (irq == 0 || irq == 0xff) {
|
||||||
|
ERROR("IRQ not assigned\n");
|
||||||
|
fStatus = B_BAD_DATA;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fSemaphore = create_sem(0, "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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First of all, we have to make sure we are in a sane state. The easiest
|
||||||
|
// way is to reset everything.
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
// Turn on the power supply to the card, if there is a card inserted
|
||||||
|
if (PowerOn()) {
|
||||||
|
// Then we configure the clock to the frequency needed for
|
||||||
|
// initialization
|
||||||
|
SetClock(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, configure some useful interrupts
|
||||||
|
EnableInterrupts(SDHCI_INT_CMD_CMP | SDHCI_INT_CARD_REM
|
||||||
|
| SDHCI_INT_TRANS_CMP);
|
||||||
|
|
||||||
|
// We want to see the error bits in the status register, but not have an
|
||||||
|
// interrupt trigger on them (we get a "command complete" interrupt on
|
||||||
|
// errors already)
|
||||||
|
fRegisters->interrupt_status_enable |= SDHCI_INT_ERROR
|
||||||
|
| SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_INDEX
|
||||||
|
| SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT;
|
||||||
|
|
||||||
|
if (poll) {
|
||||||
|
// Spawn a polling thread, as the interrupts won't currently work on ACPI.
|
||||||
|
fWorkerThread = spawn_kernel_thread(_WorkerThread, "SD bus poller",
|
||||||
|
B_NORMAL_PRIORITY, this);
|
||||||
|
resume_thread(fWorkerThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SdhciBus::~SdhciBus()
|
||||||
|
{
|
||||||
|
DisableInterrupts();
|
||||||
|
|
||||||
|
if (fSemaphore != 0)
|
||||||
|
delete_sem(fSemaphore);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
/*
|
||||||
|
PartA2, SD Host Controller Simplified Specification, Version 4.20
|
||||||
|
§3.7.1.1 The sequence to issue an SD Command
|
||||||
|
*/
|
||||||
|
status_t
|
||||||
|
SdhciBus::ExecuteCommand(uint8_t command, uint32_t argument, uint32_t* response)
|
||||||
|
{
|
||||||
|
TRACE("ExecuteCommand(%d, %x)\n", command, argument);
|
||||||
|
|
||||||
|
// First of all clear the result
|
||||||
|
fCommandResult = 0;
|
||||||
|
|
||||||
|
// Check if it's possible to send a command right now.
|
||||||
|
// It is not possible to send a command as long as the command line is busy.
|
||||||
|
// The spec says we should wait, but we can't do that on kernel side, since
|
||||||
|
// it leaves no chance for the upper layers to handle the problem. So we
|
||||||
|
// just say we're busy and the caller can retry later.
|
||||||
|
// Note that this should normally never happen: the command line is busy
|
||||||
|
// only during command execution, and we don't leave this function with ac
|
||||||
|
// command running.
|
||||||
|
if (fRegisters->present_state.CommandInhibit()) {
|
||||||
|
panic("Command execution impossible, command inhibit\n");
|
||||||
|
return B_BUSY;
|
||||||
|
}
|
||||||
|
if (fRegisters->present_state.DataInhibit()) {
|
||||||
|
panic("Command execution unwise, data inhibit\n");
|
||||||
|
return B_BUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t replyType;
|
||||||
|
|
||||||
|
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:
|
||||||
|
case SD_READ_MULTIPLE_BLOCKS:
|
||||||
|
case SD_WRITE_SINGLE_BLOCK:
|
||||||
|
case SD_WRITE_MULTIPLE_BLOCKS:
|
||||||
|
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: // SD Application command
|
||||||
|
replyType = Command::kR1Type;
|
||||||
|
break;
|
||||||
|
case SD_SEND_OP_COND: // SD Application command
|
||||||
|
replyType = Command::kR3Type;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ERROR("Unknown command %x\n", command);
|
||||||
|
return B_BAD_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if DATA line is available (if needed)
|
||||||
|
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");
|
||||||
|
|
||||||
|
if (replyType == Command::kR1bType)
|
||||||
|
fRegisters->transfer_mode = 0;
|
||||||
|
|
||||||
|
fRegisters->argument = argument;
|
||||||
|
fRegisters->command.SendCommand(command, replyType);
|
||||||
|
|
||||||
|
// Wait for command response to be available ("command complete" interrupt)
|
||||||
|
TRACE("Wait for command complete...");
|
||||||
|
do {
|
||||||
|
//fCommandResult = fRegisters->interrupt_status;
|
||||||
|
acquire_sem(fSemaphore);
|
||||||
|
TRACE("command complete sem acquired, 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_TIMEOUT) {
|
||||||
|
ERROR("Command execution timed out\n");
|
||||||
|
if (fRegisters->present_state.CommandInhibit()) {
|
||||||
|
TRACE("Command line is still busy, clearing it\n");
|
||||||
|
// Clear the stall
|
||||||
|
fRegisters->software_reset.ResetCommandLine();
|
||||||
|
}
|
||||||
|
return B_TIMED_OUT;
|
||||||
|
}
|
||||||
|
if (fCommandResult & SDHCI_INT_CRC) {
|
||||||
|
ERROR("CRC error\n");
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
ERROR("Command execution failed %x\n", fCommandResult);
|
||||||
|
// TODO look at errors in interrupt_status register for more details
|
||||||
|
// and return a more appropriate error code
|
||||||
|
return B_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fRegisters->present_state.CommandInhibit()) {
|
||||||
|
TRACE("Command execution failed, card stalled\n");
|
||||||
|
// Clear the stall
|
||||||
|
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:
|
||||||
|
// No response
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replyType == Command::kR1bType
|
||||||
|
&& (fCommandResult & SDHCI_INT_TRANS_CMP) == 0) {
|
||||||
|
// R1b commands may use the data line so we must wait for the
|
||||||
|
// "transfer complete" interrupt here.
|
||||||
|
TRACE("Waiting for data line...\n");
|
||||||
|
do {
|
||||||
|
acquire_sem(fSemaphore);
|
||||||
|
} while (fRegisters->present_state.DataInhibit());
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
int base_clock = fRegisters->capabilities.BaseClockFrequency();
|
||||||
|
// Try to get as close to 400kHz as possible, but not faster
|
||||||
|
int divider = base_clock * 1000 / kilohertz;
|
||||||
|
|
||||||
|
if (fRegisters->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;
|
||||||
|
|
||||||
|
divider--;
|
||||||
|
divider |= divider >> 1;
|
||||||
|
divider |= divider >> 2;
|
||||||
|
divider |= divider >> 4;
|
||||||
|
divider++;
|
||||||
|
}
|
||||||
|
|
||||||
|
divider = fRegisters->clock_control.SetDivider(divider);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// We have set the divider, now we can enable the internal clock.
|
||||||
|
fRegisters->clock_control.EnableInternal();
|
||||||
|
|
||||||
|
// wait until internal clock is stabilized
|
||||||
|
while (!(fRegisters->clock_control.InternalStable()));
|
||||||
|
|
||||||
|
fRegisters->clock_control.EnablePLL();
|
||||||
|
while (!(fRegisters->clock_control.InternalStable()));
|
||||||
|
|
||||||
|
// Finally, route the clock to the SD card
|
||||||
|
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_PRIu64 " bytes at %" B_PRIdOFF "\n",
|
||||||
|
isWrite ? "Write" : "Read", length, offset);
|
||||||
|
|
||||||
|
// Check that the IO scheduler did its job in following our DMA restrictions
|
||||||
|
// We can start a read only at a sector boundary
|
||||||
|
ASSERT(offset % kBlockSize == 0);
|
||||||
|
// We can only read complete sectors
|
||||||
|
ASSERT(length % kBlockSize == 0);
|
||||||
|
|
||||||
|
const generic_io_vec* vecs = operation->Vecs();
|
||||||
|
generic_size_t vecOffset = 0;
|
||||||
|
|
||||||
|
// FIXME can this be moved to the init function instead?
|
||||||
|
//
|
||||||
|
// For simplicity we use a transfer size equal to the sector size. We could
|
||||||
|
// go up to 2K here if the length to read in each individual vec is a
|
||||||
|
// multiple of 2K, but we have no easy way to know this (we would need to
|
||||||
|
// iterate through the IOOperation vecs and check the size of each of them).
|
||||||
|
// We could also do smaller transfers, but it is not possible to start a
|
||||||
|
// transfer anywhere else than the start of a sector, so it's a lot simpler
|
||||||
|
// to always work in complete sectors. We set the B_DMA_ALIGNMENT device
|
||||||
|
// node property accordingly, making sure that we don't get asked to do
|
||||||
|
// transfers that are not aligned with sectors.
|
||||||
|
//
|
||||||
|
// Additionnally, set SDMA buffer boundary aligment to 512K. This is the
|
||||||
|
// largest possible size. We also set the B_DMA_BOUNDARY property on the
|
||||||
|
// published device node, so that the DMA resource manager knows that it
|
||||||
|
// must respect this boundary. As a result, we will never be asked to
|
||||||
|
// do a transfer that crosses this boundary, and we don't need to handle
|
||||||
|
// the DMA boundary interrupt (the transfer will be split in two at an
|
||||||
|
// upper layer).
|
||||||
|
fRegisters->block_size.ConfigureTransfer(kBlockSize,
|
||||||
|
BlockSize::kDmaBoundary512K);
|
||||||
|
status_t result = B_OK;
|
||||||
|
|
||||||
|
while (length > 0) {
|
||||||
|
size_t toCopy = std::min((generic_size_t)length,
|
||||||
|
vecs->length - vecOffset);
|
||||||
|
|
||||||
|
// If the current vec is empty, we can move to the next
|
||||||
|
if (toCopy == 0) {
|
||||||
|
vecs++;
|
||||||
|
vecOffset = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// With SDMA we can only transfer multiples of 1 sector
|
||||||
|
ASSERT(toCopy % kBlockSize == 0);
|
||||||
|
|
||||||
|
fRegisters->system_address = vecs->base + vecOffset;
|
||||||
|
// fRegisters->adma_system_address = fDmaMemory;
|
||||||
|
|
||||||
|
fRegisters->block_count = toCopy / kBlockSize;
|
||||||
|
|
||||||
|
uint16 direction;
|
||||||
|
if (isWrite)
|
||||||
|
direction = TransferMode::kWrite;
|
||||||
|
else
|
||||||
|
direction = TransferMode::kRead;
|
||||||
|
fRegisters->transfer_mode = TransferMode::kMulti | direction
|
||||||
|
| TransferMode::kAutoCmd12Enable
|
||||||
|
| TransferMode::kBlockCountEnable | TransferMode::kDmaEnable;
|
||||||
|
|
||||||
|
uint32_t response;
|
||||||
|
result = ExecuteCommand(command,
|
||||||
|
offset / (offsetAsSectors ? kBlockSize : 1), &response);
|
||||||
|
if (result != B_OK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Wait for DMA transfer to complete
|
||||||
|
// In theory we could go on and send other commands as long as they
|
||||||
|
// don't need the DAT lines, but it's overcomplicating things.
|
||||||
|
TRACE("Wait for transfer complete...");
|
||||||
|
//while ((fRegisters->interrupt_status & SDHCI_INT_TRANS_CMP) == 0);
|
||||||
|
acquire_sem(fSemaphore);
|
||||||
|
TRACE("transfer complete OK.\n");
|
||||||
|
|
||||||
|
length -= toCopy;
|
||||||
|
vecOffset += toCopy;
|
||||||
|
offset += toCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
SdhciBus::SetScanSemaphore(sem_id sem)
|
||||||
|
{
|
||||||
|
fScanSemaphore = sem;
|
||||||
|
|
||||||
|
// If there is already a card in, start a scan immediately
|
||||||
|
if (fRegisters->present_state.IsCardInserted())
|
||||||
|
release_sem(fScanSemaphore);
|
||||||
|
|
||||||
|
// We can now enable the card insertion interrupt for next time a card
|
||||||
|
// is inserted
|
||||||
|
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
|
||||||
|
// We could use the slot register to quickly see for which slot the
|
||||||
|
// interrupt is. But since we have an interrupt handler call for each slot
|
||||||
|
// anyway, it's just as simple to let each of them scan its own interrupt
|
||||||
|
// status register.
|
||||||
|
if ( !(fRegisters->slot_interrupt_status & (1 << fSlot)) ) {
|
||||||
|
TRACE("interrupt not for me.\n");
|
||||||
|
return B_UNHANDLED_INTERRUPT;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint32_t intmask = fRegisters->interrupt_status;
|
||||||
|
|
||||||
|
// Shortcut: exit early if there is no interrupt or if the register is
|
||||||
|
// clearly invalid.
|
||||||
|
if ((intmask == 0) || (intmask == 0xffffffff)) {
|
||||||
|
return B_UNHANDLED_INTERRUPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE("interrupt function called %x\n", intmask);
|
||||||
|
|
||||||
|
// handling card presence interrupts
|
||||||
|
if ((intmask & SDHCI_INT_CARD_REM) != 0) {
|
||||||
|
// We can get spurious interrupts as the card is inserted or removed,
|
||||||
|
// so check the actual state before acting
|
||||||
|
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) {
|
||||||
|
// We can get spurious interrupts as the card is inserted or removed,
|
||||||
|
// so check the actual state before acting
|
||||||
|
if (fRegisters->present_state.IsCardInserted()) {
|
||||||
|
if (PowerOn())
|
||||||
|
SetClock(400);
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// handling command interrupt
|
||||||
|
if (intmask & SDHCI_INT_CMD_MASK) {
|
||||||
|
fCommandResult = intmask;
|
||||||
|
// Save the status before clearing so the thread can handle it
|
||||||
|
fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK);
|
||||||
|
|
||||||
|
// Notify the thread
|
||||||
|
release_sem_etc(fSemaphore, 1, B_DO_NOT_RESCHEDULE);
|
||||||
|
TRACE("Command complete interrupt handled\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intmask & SDHCI_INT_TRANS_CMP) {
|
||||||
|
fCommandResult = intmask;
|
||||||
|
fRegisters->interrupt_status |= SDHCI_INT_TRANS_CMP;
|
||||||
|
release_sem_etc(fSemaphore, 1, B_DO_NOT_RESCHEDULE);
|
||||||
|
TRACE("Transfer complete interrupt handled\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// handling bus power interrupt
|
||||||
|
if (intmask & SDHCI_INT_BUS_POWER) {
|
||||||
|
fRegisters->interrupt_status |= SDHCI_INT_BUS_POWER;
|
||||||
|
TRACE("card is consuming too much power\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all interrupts have been cleared (we check all the ones we
|
||||||
|
// enabled, so that should always be the case)
|
||||||
|
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);
|
||||||
|
release_sem(bus->fSemaphore);
|
||||||
|
}
|
||||||
|
if (intmask & SDHCI_INT_TRANS_CMP) {
|
||||||
|
bus->fCommandResult = intmask;
|
||||||
|
bus->fRegisters->interrupt_status |= SDHCI_INT_TRANS_CMP;
|
||||||
|
release_sem(bus->fSemaphore);
|
||||||
|
}
|
||||||
|
snooze(100);
|
||||||
|
}
|
||||||
|
TRACE("poller thread terminating");
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
|
||||||
|
void
|
||||||
|
uninit_bus(void* bus_cookie)
|
||||||
|
{
|
||||||
|
SdhciBus* bus = (SdhciBus*)bus_cookie;
|
||||||
|
delete bus;
|
||||||
|
|
||||||
|
// FIXME do we need to put() the PCI module here?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// make sure parent is either an ACPI or PCI SDHCI device node
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root device that binds to the ACPI or PCI bus. It will register an mmc_bus_interface
|
||||||
|
// node for each SD slot in the device.
|
||||||
|
static driver_module_info sSDHCIDevice = {
|
||||||
|
{
|
||||||
|
SDHCI_DEVICE_MODULE_NAME,
|
||||||
|
0,
|
||||||
|
NULL
|
||||||
|
},
|
||||||
|
supports_device,
|
||||||
|
register_device,
|
||||||
|
init_device,
|
||||||
|
uninit_device,
|
||||||
|
register_child_devices,
|
||||||
|
NULL, // rescan
|
||||||
|
NULL, // device removed
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module_info* modules[] = {
|
||||||
|
(module_info* )&sSDHCIDevice,
|
||||||
|
(module_info* )&gSDHCIPCIDeviceModule,
|
||||||
|
(module_info* )&gSDHCIACPIDeviceModule,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2018-2020 Haiku, Inc. All rights reserved.
|
* Copyright 2018-2024 Haiku, Inc. All rights reserved.
|
||||||
* Distributed under the terms of the MIT License.
|
* Distributed under the terms of the MIT License.
|
||||||
*
|
*
|
||||||
* Authors:
|
* Authors:
|
||||||
* B Krishnan Iyer, krishnaniyer97@gmail.com
|
* B Krishnan Iyer, krishnaniyer97@gmail.com
|
||||||
|
* Ron Ben Aroya, sed4906birdie@gmail.com
|
||||||
*/
|
*/
|
||||||
#ifndef _SDHCI_PCI_H
|
#ifndef _SDHCI_H
|
||||||
#define _SDHCI_PCI_H
|
#define _SDHCI_H
|
||||||
|
|
||||||
|
|
||||||
#include <device_manager.h>
|
#include <device_manager.h>
|
||||||
@@ -27,6 +28,46 @@
|
|||||||
#define SDHCI_BUS_TYPE_NAME "bus/sdhci/v1"
|
#define SDHCI_BUS_TYPE_NAME "bus/sdhci/v1"
|
||||||
|
|
||||||
|
|
||||||
|
class SdhciBus {
|
||||||
|
public:
|
||||||
|
SdhciBus(struct registers* registers, uint8_t irq, bool poll);
|
||||||
|
~SdhciBus();
|
||||||
|
|
||||||
|
void EnableInterrupts(uint32_t mask);
|
||||||
|
void DisableInterrupts();
|
||||||
|
status_t ExecuteCommand(uint8_t command, uint32_t argument,
|
||||||
|
uint32_t* response);
|
||||||
|
int32 HandleInterrupt();
|
||||||
|
status_t InitCheck();
|
||||||
|
void Reset();
|
||||||
|
void SetClock(int kilohertz);
|
||||||
|
status_t DoIO(uint8_t command, IOOperation* operation,
|
||||||
|
bool offsetAsSectors);
|
||||||
|
void SetScanSemaphore(sem_id sem);
|
||||||
|
void SetBusWidth(int width);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool PowerOn();
|
||||||
|
void RecoverError();
|
||||||
|
static status_t _WorkerThread(void*);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct registers* fRegisters;
|
||||||
|
uint32_t fCommandResult;
|
||||||
|
uint8_t fIrq;
|
||||||
|
sem_id fSemaphore;
|
||||||
|
sem_id fScanSemaphore;
|
||||||
|
status_t fStatus;
|
||||||
|
thread_id fWorkerThread;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class SdhciDevice {
|
||||||
|
public:
|
||||||
|
device_node* fNode;
|
||||||
|
uint8_t fRicohOriginalMode;
|
||||||
|
};
|
||||||
|
|
||||||
class TransferMode {
|
class TransferMode {
|
||||||
public:
|
public:
|
||||||
uint16_t Bits() { return fBits; }
|
uint16_t Bits() { return fBits; }
|
||||||
@@ -368,6 +409,42 @@ struct registers {
|
|||||||
|
|
||||||
typedef void* sdhci_mmc_bus;
|
typedef void* sdhci_mmc_bus;
|
||||||
|
|
||||||
|
struct sdhci_crs {
|
||||||
|
uint8 irq;
|
||||||
|
// uint8 irq_triggering;
|
||||||
|
// uint8 irq_polarity;
|
||||||
|
// uint8 irq_shareable;
|
||||||
|
|
||||||
|
uint32 addr_bas;
|
||||||
|
uint32 addr_len;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /*_SDHCI_PCI_H*/
|
extern float supports_device_acpi(device_node* parent);
|
||||||
|
extern float supports_device_pci(device_node* parent);
|
||||||
|
|
||||||
|
extern status_t register_child_devices_acpi(void* cookie);
|
||||||
|
extern status_t register_child_devices_pci(void* cookie);
|
||||||
|
|
||||||
|
extern status_t init_device_pci(device_node* node, SdhciDevice* context);
|
||||||
|
extern void uninit_device_pci(SdhciDevice* context, device_node* pciParent);
|
||||||
|
|
||||||
|
extern status_t init_bus_acpi(device_node* node, void** bus_cookie);
|
||||||
|
extern status_t init_bus_pci(device_node* node, void** bus_cookie);
|
||||||
|
|
||||||
|
extern void uninit_bus(void* bus_cookie);
|
||||||
|
extern void bus_removed(void* bus_cookie);
|
||||||
|
|
||||||
|
status_t set_clock(void* controller, uint32_t kilohertz);
|
||||||
|
status_t execute_command(void* controller, uint8_t command,
|
||||||
|
uint32_t argument, uint32_t* response);
|
||||||
|
status_t do_io(void* controller, uint8_t command,
|
||||||
|
IOOperation* operation, bool offsetAsSectors);
|
||||||
|
void set_scan_semaphore(void* controller, sem_id sem);
|
||||||
|
void set_bus_width(void* controller, int width);
|
||||||
|
|
||||||
|
extern mmc_bus_interface gSDHCIACPIDeviceModule;
|
||||||
|
extern mmc_bus_interface gSDHCIPCIDeviceModule;
|
||||||
|
|
||||||
|
extern device_manager_info* gDeviceManager;
|
||||||
|
|
||||||
|
#endif /*_SDHCI_H*/
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2024 Haiku, Inc. All rights reserved.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* B Krishnan Iyer, [email protected]
|
||||||
|
* Adrien Destugues, [email protected]
|
||||||
|
* Ron Ben Aroya, [email protected]
|
||||||
|
*/
|
||||||
|
#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"
|
||||||
|
#define SDHCI_ACPI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci/acpi/device/v1"
|
||||||
|
|
||||||
|
static acpi_status
|
||||||
|
sdhci_acpi_scan_parse_callback(ACPI_RESOURCE *res, void *context)
|
||||||
|
{
|
||||||
|
struct sdhci_crs* crs = (struct sdhci_crs*)context;
|
||||||
|
|
||||||
|
if (res->Type == ACPI_RESOURCE_TYPE_FIXED_MEMORY32) {
|
||||||
|
crs->addr_bas = res->Data.FixedMemory32.Address;
|
||||||
|
crs->addr_len = res->Data.FixedMemory32.AddressLength;
|
||||||
|
} else if (res->Type == ACPI_RESOURCE_TYPE_IRQ) {
|
||||||
|
crs->irq = res->Data.Irq.Interrupt;
|
||||||
|
//crs->irq_triggering = res->Data.Irq.Triggering;
|
||||||
|
//crs->irq_polarity = res->Data.Irq.Polarity;
|
||||||
|
//crs->irq_shareable = res->Data.Irq.Shareable;
|
||||||
|
} else if (res->Type == ACPI_RESOURCE_TYPE_EXTENDED_IRQ) {
|
||||||
|
crs->irq = res->Data.ExtendedIrq.Interrupt;
|
||||||
|
//crs->irq_triggering = res->Data.ExtendedIrq.Triggering;
|
||||||
|
//crs->irq_polarity = res->Data.ExtendedIrq.Polarity;
|
||||||
|
//crs->irq_shareable = res->Data.ExtendedIrq.Shareable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t
|
||||||
|
init_bus_acpi(device_node* node, void** bus_cookie)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
// Get the ACPI driver and device
|
||||||
|
acpi_device_module_info* acpi;
|
||||||
|
acpi_device device;
|
||||||
|
|
||||||
|
device_node* parent = gDeviceManager->get_parent_node(node);
|
||||||
|
device_node* acpiParent = gDeviceManager->get_parent_node(parent);
|
||||||
|
gDeviceManager->get_driver(acpiParent, (driver_module_info**)&acpi,
|
||||||
|
(void**)&device);
|
||||||
|
gDeviceManager->put_node(acpiParent);
|
||||||
|
gDeviceManager->put_node(parent);
|
||||||
|
|
||||||
|
// Ignore invalid bars
|
||||||
|
TRACE("Register SD bus\n");
|
||||||
|
|
||||||
|
struct sdhci_crs crs;
|
||||||
|
if(acpi->walk_resources(device, (ACPI_STRING)"_CRS",
|
||||||
|
sdhci_acpi_scan_parse_callback, &crs) != B_OK) {
|
||||||
|
ERROR("Couldn't scan ACPI register set\n");
|
||||||
|
return B_IO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE("addr: %" B_PRIx32 " len: %" B_PRIx32 "\n", crs.addr_bas, crs.addr_len);
|
||||||
|
|
||||||
|
if (crs.addr_bas == 0 || crs.addr_len == 0) {
|
||||||
|
ERROR("No registers to map\n");
|
||||||
|
return B_IO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// map the slot registers
|
||||||
|
area_id regs_area;
|
||||||
|
struct registers* _regs;
|
||||||
|
regs_area = map_physical_memory("sdhc_regs_map",
|
||||||
|
crs.addr_bas, crs.addr_len, B_ANY_KERNEL_BLOCK_ADDRESS,
|
||||||
|
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&_regs);
|
||||||
|
|
||||||
|
if (regs_area < B_OK) {
|
||||||
|
ERROR("Could not map registers\n");
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the interrupt is shared between all busses in an SDHC controller, but
|
||||||
|
// they each register an handler. Not a problem, we will just test the
|
||||||
|
// interrupt registers for all busses one after the other and find no
|
||||||
|
// interrupts on the idle busses.
|
||||||
|
uint8_t irq = crs.irq;
|
||||||
|
TRACE("irq interrupt line: %d\n", irq);
|
||||||
|
|
||||||
|
SdhciBus* bus = new(std::nothrow) SdhciBus(_regs, irq, true);
|
||||||
|
|
||||||
|
status_t status = B_NO_MEMORY;
|
||||||
|
if (bus != NULL)
|
||||||
|
status = bus->InitCheck();
|
||||||
|
|
||||||
|
if (status != B_OK) {
|
||||||
|
if (bus != NULL)
|
||||||
|
delete bus;
|
||||||
|
else
|
||||||
|
delete_area(regs_area);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the created object as a cookie, allowing users of the bus to
|
||||||
|
// locate it.
|
||||||
|
*bus_cookie = bus;
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t
|
||||||
|
register_child_devices_acpi(void* cookie)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
SdhciDevice* context = (SdhciDevice*)cookie;
|
||||||
|
device_node* parent = gDeviceManager->get_parent_node(context->fNode);
|
||||||
|
acpi_device_module_info* acpi;
|
||||||
|
acpi_device* device;
|
||||||
|
|
||||||
|
gDeviceManager->get_driver(parent, (driver_module_info**)&acpi,
|
||||||
|
(void**)&device);
|
||||||
|
|
||||||
|
TRACE("register_child_devices\n");
|
||||||
|
|
||||||
|
char prettyName[25];
|
||||||
|
|
||||||
|
sprintf(prettyName, "SDHC bus");
|
||||||
|
device_attr attrs[] = {
|
||||||
|
// properties of this controller for mmc bus manager
|
||||||
|
{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = prettyName } },
|
||||||
|
{ B_DEVICE_FIXED_CHILD, B_STRING_TYPE,
|
||||||
|
{.string = MMC_BUS_MODULE_NAME} },
|
||||||
|
{ B_DEVICE_BUS, B_STRING_TYPE, {.string = "mmc"} },
|
||||||
|
|
||||||
|
// DMA properties
|
||||||
|
// The high alignment is to force access only to complete sectors
|
||||||
|
// These constraints could be removed by using ADMA which allows
|
||||||
|
// use of the full 64bit address space and can do scatter-gather.
|
||||||
|
{ B_DMA_ALIGNMENT, B_UINT32_TYPE, { .ui32 = 511 }},
|
||||||
|
{ B_DMA_HIGH_ADDRESS, B_UINT64_TYPE, { .ui64 = 0x100000000LL }},
|
||||||
|
{ B_DMA_BOUNDARY, B_UINT32_TYPE, { .ui32 = (1 << 19) - 1 }},
|
||||||
|
{ B_DMA_MAX_SEGMENT_COUNT, B_UINT32_TYPE, { .ui32 = 1 }},
|
||||||
|
{ B_DMA_MAX_SEGMENT_BLOCKS, B_UINT32_TYPE, { .ui32 = (1 << 10) - 1 }},
|
||||||
|
|
||||||
|
// private data to identify device
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
device_node* node;
|
||||||
|
if (gDeviceManager->register_node(context->fNode,
|
||||||
|
SDHCI_ACPI_MMC_BUS_MODULE_NAME, attrs, NULL,
|
||||||
|
&node) != B_OK)
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
float
|
||||||
|
supports_device_acpi(device_node* parent)
|
||||||
|
{
|
||||||
|
const char* hid;
|
||||||
|
const char* uid;
|
||||||
|
uint32 type;
|
||||||
|
|
||||||
|
if (gDeviceManager->get_attr_uint32(parent, ACPI_DEVICE_TYPE_ITEM, &type, false)
|
||||||
|
|| type != ACPI_TYPE_DEVICE) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gDeviceManager->get_attr_string(parent, ACPI_DEVICE_HID_ITEM, &hid, false)) {
|
||||||
|
TRACE("No hid attribute\n");
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gDeviceManager->get_attr_string(parent, ACPI_DEVICE_UID_ITEM, &uid, false)) {
|
||||||
|
TRACE("No uid attribute\n");
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE("supports_device(hid:%s uid:%s)\n", hid, uid);
|
||||||
|
|
||||||
|
if (!(strcmp(hid, "80860F14") == 0
|
||||||
|
|| strcmp(hid, "80860F16") == 0
|
||||||
|
|| strcmp(hid, "80865ACA") == 0
|
||||||
|
|| strcmp(hid, "80865AD0") == 0
|
||||||
|
|| strcmp(hid, "INT33C6") == 0
|
||||||
|
|| strcmp(hid, "INT3436") == 0
|
||||||
|
|| strcmp(hid, "INT344D") == 0
|
||||||
|
|| strcmp(hid, "INT33BB") == 0
|
||||||
|
|| strcmp(hid, "NXP0003") == 0
|
||||||
|
|| strcmp(hid, "RKCP0D40") == 0
|
||||||
|
|| strcmp(hid, "PNP0D40") == 0))
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
acpi_device_module_info* acpi;
|
||||||
|
acpi_device* device;
|
||||||
|
gDeviceManager->get_driver(parent, (driver_module_info**)&acpi,
|
||||||
|
(void**)&device);
|
||||||
|
TRACE("SDHCI Device found! hid: %s, uid: %s\n", hid, uid);
|
||||||
|
|
||||||
|
return 0.8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device node registered for each SD slot. It implements the MMC operations so
|
||||||
|
// the bus manager can use it to communicate with SD cards.
|
||||||
|
mmc_bus_interface gSDHCIACPIDeviceModule = {
|
||||||
|
.info = {
|
||||||
|
.info = {
|
||||||
|
.name = SDHCI_ACPI_MMC_BUS_MODULE_NAME,
|
||||||
|
},
|
||||||
|
|
||||||
|
.init_driver = init_bus_acpi,
|
||||||
|
.uninit_driver = uninit_bus,
|
||||||
|
.device_removed = bus_removed,
|
||||||
|
},
|
||||||
|
|
||||||
|
.set_clock = set_clock,
|
||||||
|
.execute_command = execute_command,
|
||||||
|
.do_io = do_io,
|
||||||
|
.set_scan_semaphore = set_scan_semaphore,
|
||||||
|
.set_bus_width = set_bus_width,
|
||||||
|
};
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2018-2020 Haiku, Inc. All rights reserved.
|
* Copyright 2018-2024 Haiku, Inc. All rights reserved.
|
||||||
* Distributed under the terms of the MIT License.
|
* Distributed under the terms of the MIT License.
|
||||||
*
|
*
|
||||||
* Authors:
|
* Authors:
|
||||||
* B Krishnan Iyer, [email protected]
|
* B Krishnan Iyer, [email protected]
|
||||||
* Adrien Destugues, [email protected]
|
* Adrien Destugues, [email protected]
|
||||||
|
* Ron Ben Aroya, [email protected]
|
||||||
*/
|
*/
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <new>
|
#include <new>
|
||||||
@@ -12,637 +13,35 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <bus/PCI.h>
|
#include <bus/PCI.h>
|
||||||
|
#include <ACPI.h>
|
||||||
|
#include "acpi.h"
|
||||||
|
|
||||||
#include <KernelExport.h>
|
#include <KernelExport.h>
|
||||||
|
|
||||||
#include "IOSchedulerSimple.h"
|
#include "IOSchedulerSimple.h"
|
||||||
#include "mmc.h"
|
#include "mmc.h"
|
||||||
#include "sdhci_pci.h"
|
#include "sdhci.h"
|
||||||
|
|
||||||
|
|
||||||
#define TRACE_SDHCI
|
#define TRACE_SDHCI
|
||||||
#ifdef TRACE_SDHCI
|
#ifdef TRACE_SDHCI
|
||||||
# define TRACE(x...) dprintf("\33[33msdhci_pci:\33[0m " x)
|
# define TRACE(x...) dprintf("\33[33msdhci:\33[0m " x)
|
||||||
#else
|
#else
|
||||||
# define TRACE(x...) ;
|
# define TRACE(x...) ;
|
||||||
#endif
|
#endif
|
||||||
#define TRACE_ALWAYS(x...) dprintf("\33[33msdhci_pci:\33[0m " x)
|
#define TRACE_ALWAYS(x...) dprintf("\33[33msdhci:\33[0m " x)
|
||||||
#define ERROR(x...) dprintf("\33[33msdhci_pci:\33[0m " x)
|
#define ERROR(x...) dprintf("\33[33msdhci:\33[0m " x)
|
||||||
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
|
#define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
|
||||||
|
|
||||||
|
|
||||||
#define SDHCI_PCI_DEVICE_MODULE_NAME "busses/mmc/sdhci_pci/driver_v1"
|
#define SDHCI_DEVICE_MODULE_NAME "busses/mmc/sdhci/driver_v1"
|
||||||
#define SDHCI_PCI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci_pci/device/v1"
|
#define SDHCI_PCI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci/pci/device/v1"
|
||||||
|
|
||||||
#define SLOT_NUMBER "device/slot"
|
#define SLOT_NUMBER "device/slot"
|
||||||
#define BAR_INDEX "device/bar"
|
#define BAR_INDEX "device/bar"
|
||||||
|
|
||||||
|
|
||||||
class SdhciBus {
|
|
||||||
public:
|
|
||||||
SdhciBus(struct registers* registers, uint8_t irq);
|
|
||||||
~SdhciBus();
|
|
||||||
|
|
||||||
void EnableInterrupts(uint32_t mask);
|
|
||||||
void DisableInterrupts();
|
|
||||||
status_t ExecuteCommand(uint8_t command, uint32_t argument,
|
|
||||||
uint32_t* response);
|
|
||||||
int32 HandleInterrupt();
|
|
||||||
status_t InitCheck();
|
|
||||||
void Reset();
|
|
||||||
void SetClock(int kilohertz);
|
|
||||||
status_t DoIO(uint8_t command, IOOperation* operation,
|
|
||||||
bool offsetAsSectors);
|
|
||||||
void SetScanSemaphore(sem_id sem);
|
|
||||||
void SetBusWidth(int width);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool PowerOn();
|
|
||||||
void RecoverError();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct registers* fRegisters;
|
|
||||||
uint32_t fCommandResult;
|
|
||||||
uint8_t fIrq;
|
|
||||||
sem_id fSemaphore;
|
|
||||||
sem_id fScanSemaphore;
|
|
||||||
status_t fStatus;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class SdhciDevice {
|
|
||||||
public:
|
|
||||||
device_node* fNode;
|
|
||||||
uint8_t fRicohOriginalMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
:
|
|
||||||
fRegisters(registers),
|
|
||||||
fIrq(irq),
|
|
||||||
fSemaphore(0)
|
|
||||||
{
|
|
||||||
if (irq == 0 || irq == 0xff) {
|
|
||||||
ERROR("PCI IRQ not assigned\n");
|
|
||||||
fStatus = B_BAD_DATA;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fSemaphore = create_sem(0, "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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First of all, we have to make sure we are in a sane state. The easiest
|
|
||||||
// way is to reset everything.
|
|
||||||
Reset();
|
|
||||||
|
|
||||||
// Turn on the power supply to the card, if there is a card inserted
|
|
||||||
if (PowerOn()) {
|
|
||||||
// Then we configure the clock to the frequency needed for
|
|
||||||
// initialization
|
|
||||||
SetClock(400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, configure some useful interrupts
|
|
||||||
EnableInterrupts(SDHCI_INT_CMD_CMP | SDHCI_INT_CARD_REM
|
|
||||||
| SDHCI_INT_TRANS_CMP);
|
|
||||||
|
|
||||||
// We want to see the error bits in the status register, but not have an
|
|
||||||
// interrupt trigger on them (we get a "command complete" interrupt on
|
|
||||||
// errors already)
|
|
||||||
fRegisters->interrupt_status_enable |= SDHCI_INT_ERROR
|
|
||||||
| SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_INDEX
|
|
||||||
| SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SdhciBus::~SdhciBus()
|
|
||||||
{
|
|
||||||
DisableInterrupts();
|
|
||||||
|
|
||||||
if (fSemaphore != 0)
|
|
||||||
delete_sem(fSemaphore);
|
|
||||||
|
|
||||||
if (fIrq != 0)
|
|
||||||
remove_io_interrupt_handler(fIrq, sdhci_generic_interrupt, this);
|
|
||||||
|
|
||||||
area_id regs_area = area_for(fRegisters);
|
|
||||||
delete_area(regs_area);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark -
|
|
||||||
/*
|
|
||||||
PartA2, SD Host Controller Simplified Specification, Version 4.20
|
|
||||||
§3.7.1.1 The sequence to issue an SD Command
|
|
||||||
*/
|
|
||||||
status_t
|
status_t
|
||||||
SdhciBus::ExecuteCommand(uint8_t command, uint32_t argument, uint32_t* response)
|
init_bus_pci(device_node* node, void** bus_cookie)
|
||||||
{
|
|
||||||
TRACE("ExecuteCommand(%d, %x)\n", command, argument);
|
|
||||||
|
|
||||||
// First of all clear the result
|
|
||||||
fCommandResult = 0;
|
|
||||||
|
|
||||||
// Check if it's possible to send a command right now.
|
|
||||||
// It is not possible to send a command as long as the command line is busy.
|
|
||||||
// The spec says we should wait, but we can't do that on kernel side, since
|
|
||||||
// it leaves no chance for the upper layers to handle the problem. So we
|
|
||||||
// just say we're busy and the caller can retry later.
|
|
||||||
// Note that this should normally never happen: the command line is busy
|
|
||||||
// only during command execution, and we don't leave this function with ac
|
|
||||||
// command running.
|
|
||||||
if (fRegisters->present_state.CommandInhibit()) {
|
|
||||||
panic("Command execution impossible, command inhibit\n");
|
|
||||||
return B_BUSY;
|
|
||||||
}
|
|
||||||
if (fRegisters->present_state.DataInhibit()) {
|
|
||||||
panic("Command execution unwise, data inhibit\n");
|
|
||||||
return B_BUSY;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t replyType;
|
|
||||||
|
|
||||||
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:
|
|
||||||
case SD_READ_MULTIPLE_BLOCKS:
|
|
||||||
case SD_WRITE_SINGLE_BLOCK:
|
|
||||||
case SD_WRITE_MULTIPLE_BLOCKS:
|
|
||||||
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: // SD Application command
|
|
||||||
replyType = Command::kR1Type;
|
|
||||||
break;
|
|
||||||
case SD_SEND_OP_COND: // SD Application command
|
|
||||||
replyType = Command::kR3Type;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ERROR("Unknown command %x\n", command);
|
|
||||||
return B_BAD_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if DATA line is available (if needed)
|
|
||||||
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");
|
|
||||||
|
|
||||||
if (replyType == Command::kR1bType)
|
|
||||||
fRegisters->transfer_mode = 0;
|
|
||||||
|
|
||||||
fRegisters->argument = argument;
|
|
||||||
fRegisters->command.SendCommand(command, replyType);
|
|
||||||
|
|
||||||
// Wait for command response to be available ("command complete" interrupt)
|
|
||||||
TRACE("Wait for command complete...");
|
|
||||||
do {
|
|
||||||
acquire_sem(fSemaphore);
|
|
||||||
TRACE("command complete sem acquired, 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_TIMEOUT) {
|
|
||||||
ERROR("Command execution timed out\n");
|
|
||||||
if (fRegisters->present_state.CommandInhibit()) {
|
|
||||||
TRACE("Command line is still busy, clearing it\n");
|
|
||||||
// Clear the stall
|
|
||||||
fRegisters->software_reset.ResetCommandLine();
|
|
||||||
}
|
|
||||||
return B_TIMED_OUT;
|
|
||||||
}
|
|
||||||
if (fCommandResult & SDHCI_INT_CRC) {
|
|
||||||
ERROR("CRC error\n");
|
|
||||||
return B_BAD_VALUE;
|
|
||||||
}
|
|
||||||
ERROR("Command execution failed %x\n", fCommandResult);
|
|
||||||
// TODO look at errors in interrupt_status register for more details
|
|
||||||
// and return a more appropriate error code
|
|
||||||
return B_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fRegisters->present_state.CommandInhibit()) {
|
|
||||||
TRACE("Command execution failed, card stalled\n");
|
|
||||||
// Clear the stall
|
|
||||||
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:
|
|
||||||
// No response
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replyType == Command::kR1bType
|
|
||||||
&& (fCommandResult & SDHCI_INT_TRANS_CMP) == 0) {
|
|
||||||
// R1b commands may use the data line so we must wait for the
|
|
||||||
// "transfer complete" interrupt here.
|
|
||||||
TRACE("Waiting for data line...\n");
|
|
||||||
do {
|
|
||||||
acquire_sem(fSemaphore);
|
|
||||||
} while (fRegisters->present_state.DataInhibit());
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
int base_clock = fRegisters->capabilities.BaseClockFrequency();
|
|
||||||
// Try to get as close to 400kHz as possible, but not faster
|
|
||||||
int divider = base_clock * 1000 / kilohertz;
|
|
||||||
|
|
||||||
if (fRegisters->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;
|
|
||||||
|
|
||||||
divider--;
|
|
||||||
divider |= divider >> 1;
|
|
||||||
divider |= divider >> 2;
|
|
||||||
divider |= divider >> 4;
|
|
||||||
divider++;
|
|
||||||
}
|
|
||||||
|
|
||||||
divider = fRegisters->clock_control.SetDivider(divider);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// We have set the divider, now we can enable the internal clock.
|
|
||||||
fRegisters->clock_control.EnableInternal();
|
|
||||||
|
|
||||||
// wait until internal clock is stabilized
|
|
||||||
while (!(fRegisters->clock_control.InternalStable()));
|
|
||||||
|
|
||||||
fRegisters->clock_control.EnablePLL();
|
|
||||||
while (!(fRegisters->clock_control.InternalStable()));
|
|
||||||
|
|
||||||
// Finally, route the clock to the SD card
|
|
||||||
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_PRIu64 " bytes at %" B_PRIdOFF "\n",
|
|
||||||
isWrite ? "Write" : "Read", length, offset);
|
|
||||||
|
|
||||||
// Check that the IO scheduler did its job in following our DMA restrictions
|
|
||||||
// We can start a read only at a sector boundary
|
|
||||||
ASSERT(offset % kBlockSize == 0);
|
|
||||||
// We can only read complete sectors
|
|
||||||
ASSERT(length % kBlockSize == 0);
|
|
||||||
|
|
||||||
const generic_io_vec* vecs = operation->Vecs();
|
|
||||||
generic_size_t vecOffset = 0;
|
|
||||||
|
|
||||||
// FIXME can this be moved to the init function instead?
|
|
||||||
//
|
|
||||||
// For simplicity we use a transfer size equal to the sector size. We could
|
|
||||||
// go up to 2K here if the length to read in each individual vec is a
|
|
||||||
// multiple of 2K, but we have no easy way to know this (we would need to
|
|
||||||
// iterate through the IOOperation vecs and check the size of each of them).
|
|
||||||
// We could also do smaller transfers, but it is not possible to start a
|
|
||||||
// transfer anywhere else than the start of a sector, so it's a lot simpler
|
|
||||||
// to always work in complete sectors. We set the B_DMA_ALIGNMENT device
|
|
||||||
// node property accordingly, making sure that we don't get asked to do
|
|
||||||
// transfers that are not aligned with sectors.
|
|
||||||
//
|
|
||||||
// Additionnally, set SDMA buffer boundary aligment to 512K. This is the
|
|
||||||
// largest possible size. We also set the B_DMA_BOUNDARY property on the
|
|
||||||
// published device node, so that the DMA resource manager knows that it
|
|
||||||
// must respect this boundary. As a result, we will never be asked to
|
|
||||||
// do a transfer that crosses this boundary, and we don't need to handle
|
|
||||||
// the DMA boundary interrupt (the transfer will be split in two at an
|
|
||||||
// upper layer).
|
|
||||||
fRegisters->block_size.ConfigureTransfer(kBlockSize,
|
|
||||||
BlockSize::kDmaBoundary512K);
|
|
||||||
status_t result = B_OK;
|
|
||||||
|
|
||||||
while (length > 0) {
|
|
||||||
size_t toCopy = std::min((generic_size_t)length,
|
|
||||||
vecs->length - vecOffset);
|
|
||||||
|
|
||||||
// If the current vec is empty, we can move to the next
|
|
||||||
if (toCopy == 0) {
|
|
||||||
vecs++;
|
|
||||||
vecOffset = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// With SDMA we can only transfer multiples of 1 sector
|
|
||||||
ASSERT(toCopy % kBlockSize == 0);
|
|
||||||
|
|
||||||
fRegisters->system_address = vecs->base + vecOffset;
|
|
||||||
// fRegisters->adma_system_address = fDmaMemory;
|
|
||||||
|
|
||||||
fRegisters->block_count = toCopy / kBlockSize;
|
|
||||||
|
|
||||||
uint16 direction;
|
|
||||||
if (isWrite)
|
|
||||||
direction = TransferMode::kWrite;
|
|
||||||
else
|
|
||||||
direction = TransferMode::kRead;
|
|
||||||
fRegisters->transfer_mode = TransferMode::kMulti | direction
|
|
||||||
| TransferMode::kAutoCmd12Enable
|
|
||||||
| TransferMode::kBlockCountEnable | TransferMode::kDmaEnable;
|
|
||||||
|
|
||||||
uint32_t response;
|
|
||||||
result = ExecuteCommand(command,
|
|
||||||
offset / (offsetAsSectors ? kBlockSize : 1), &response);
|
|
||||||
if (result != B_OK)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Wait for DMA transfer to complete
|
|
||||||
// In theory we could go on and send other commands as long as they
|
|
||||||
// don't need the DAT lines, but it's overcomplicating things.
|
|
||||||
TRACE("Wait for transfer complete...");
|
|
||||||
acquire_sem(fSemaphore);
|
|
||||||
TRACE("transfer complete OK.\n");
|
|
||||||
|
|
||||||
length -= toCopy;
|
|
||||||
vecOffset += toCopy;
|
|
||||||
offset += toCopy;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SdhciBus::SetScanSemaphore(sem_id sem)
|
|
||||||
{
|
|
||||||
fScanSemaphore = sem;
|
|
||||||
|
|
||||||
// If there is already a card in, start a scan immediately
|
|
||||||
if (fRegisters->present_state.IsCardInserted())
|
|
||||||
release_sem(fScanSemaphore);
|
|
||||||
|
|
||||||
// We can now enable the card insertion interrupt for next time a card
|
|
||||||
// is inserted
|
|
||||||
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
|
|
||||||
// We could use the slot register to quickly see for which slot the
|
|
||||||
// interrupt is. But since we have an interrupt handler call for each slot
|
|
||||||
// anyway, it's just as simple to let each of them scan its own interrupt
|
|
||||||
// status register.
|
|
||||||
if ( !(fRegisters->slot_interrupt_status & (1 << fSlot)) ) {
|
|
||||||
TRACE("interrupt not for me.\n");
|
|
||||||
return B_UNHANDLED_INTERRUPT;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint32_t intmask = fRegisters->interrupt_status;
|
|
||||||
|
|
||||||
// Shortcut: exit early if there is no interrupt or if the register is
|
|
||||||
// clearly invalid.
|
|
||||||
if ((intmask == 0) || (intmask == 0xffffffff)) {
|
|
||||||
return B_UNHANDLED_INTERRUPT;
|
|
||||||
}
|
|
||||||
|
|
||||||
TRACE("interrupt function called %x\n", intmask);
|
|
||||||
|
|
||||||
// handling card presence interrupts
|
|
||||||
if ((intmask & SDHCI_INT_CARD_REM) != 0) {
|
|
||||||
// We can get spurious interrupts as the card is inserted or removed,
|
|
||||||
// so check the actual state before acting
|
|
||||||
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) {
|
|
||||||
// We can get spurious interrupts as the card is inserted or removed,
|
|
||||||
// so check the actual state before acting
|
|
||||||
if (fRegisters->present_state.IsCardInserted()) {
|
|
||||||
if (PowerOn())
|
|
||||||
SetClock(400);
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// handling command interrupt
|
|
||||||
if (intmask & SDHCI_INT_CMD_MASK) {
|
|
||||||
fCommandResult = intmask;
|
|
||||||
// Save the status before clearing so the thread can handle it
|
|
||||||
fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK);
|
|
||||||
|
|
||||||
// Notify the thread
|
|
||||||
release_sem_etc(fSemaphore, 1, B_DO_NOT_RESCHEDULE);
|
|
||||||
TRACE("Command complete interrupt handled\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intmask & SDHCI_INT_TRANS_CMP) {
|
|
||||||
fCommandResult = intmask;
|
|
||||||
fRegisters->interrupt_status |= SDHCI_INT_TRANS_CMP;
|
|
||||||
release_sem_etc(fSemaphore, 1, B_DO_NOT_RESCHEDULE);
|
|
||||||
TRACE("Transfer complete interrupt handled\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// handling bus power interrupt
|
|
||||||
if (intmask & SDHCI_INT_BUS_POWER) {
|
|
||||||
fRegisters->interrupt_status |= SDHCI_INT_BUS_POWER;
|
|
||||||
TRACE("card is consuming too much power\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that all interrupts have been cleared (we check all the ones we
|
|
||||||
// enabled, so that should always be the case)
|
|
||||||
intmask = fRegisters->interrupt_status;
|
|
||||||
if (intmask != 0) {
|
|
||||||
ERROR("Remaining interrupts at end of handler: %x\n", intmask);
|
|
||||||
}
|
|
||||||
|
|
||||||
return B_HANDLED_INTERRUPT;
|
|
||||||
}
|
|
||||||
// #pragma mark -
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
init_bus(device_node* node, void** bus_cookie)
|
|
||||||
{
|
{
|
||||||
CALLED();
|
CALLED();
|
||||||
|
|
||||||
@@ -713,7 +112,7 @@ init_bus(device_node* node, void** bus_cookie)
|
|||||||
uint8_t irq = pciInfo.u.h0.interrupt_line;
|
uint8_t irq = pciInfo.u.h0.interrupt_line;
|
||||||
TRACE("irq interrupt line: %d\n", irq);
|
TRACE("irq interrupt line: %d\n", irq);
|
||||||
|
|
||||||
SdhciBus* bus = new(std::nothrow) SdhciBus(_regs, irq);
|
SdhciBus* bus = new(std::nothrow) SdhciBus(_regs, irq, false);
|
||||||
|
|
||||||
status_t status = B_NO_MEMORY;
|
status_t status = B_NO_MEMORY;
|
||||||
if (bus != NULL)
|
if (bus != NULL)
|
||||||
@@ -734,26 +133,8 @@ init_bus(device_node* node, void** bus_cookie)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status_t
|
||||||
static void
|
register_child_devices_pci(void* cookie)
|
||||||
uninit_bus(void* bus_cookie)
|
|
||||||
{
|
|
||||||
SdhciBus* bus = (SdhciBus*)bus_cookie;
|
|
||||||
delete bus;
|
|
||||||
|
|
||||||
// FIXME do we need to put() the PCI module here?
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
bus_removed(void* bus_cookie)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
register_child_devices(void* cookie)
|
|
||||||
{
|
{
|
||||||
CALLED();
|
CALLED();
|
||||||
SdhciDevice* context = (SdhciDevice*)cookie;
|
SdhciDevice* context = (SdhciDevice*)cookie;
|
||||||
@@ -809,28 +190,19 @@ register_child_devices(void* cookie)
|
|||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status_t
|
||||||
static status_t
|
init_device_pci(device_node* node, SdhciDevice* context)
|
||||||
init_device(device_node* node, void** device_cookie)
|
|
||||||
{
|
{
|
||||||
CALLED();
|
|
||||||
|
|
||||||
// Get the PCI driver and device
|
// Get the PCI driver and device
|
||||||
pci_device_module_info* pci;
|
pci_device_module_info* pci;
|
||||||
pci_device* device;
|
pci_device* device;
|
||||||
uint16 vendorId, deviceId;
|
uint16 vendorId, deviceId;
|
||||||
|
|
||||||
device_node* pciParent = gDeviceManager->get_parent_node(node);
|
device_node* pciParent = gDeviceManager->get_parent_node(context->fNode);
|
||||||
gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci,
|
gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci,
|
||||||
(void**)&device);
|
(void**)&device);
|
||||||
gDeviceManager->put_node(pciParent);
|
gDeviceManager->put_node(pciParent);
|
||||||
|
|
||||||
SdhciDevice* context = new(std::nothrow)SdhciDevice;
|
|
||||||
if (context == NULL)
|
|
||||||
return B_NO_MEMORY;
|
|
||||||
context->fNode = node;
|
|
||||||
*device_cookie = context;
|
|
||||||
|
|
||||||
if (gDeviceManager->get_attr_uint16(node, B_DEVICE_VENDOR_ID,
|
if (gDeviceManager->get_attr_uint16(node, B_DEVICE_VENDOR_ID,
|
||||||
&vendorId, true) != B_OK
|
&vendorId, true) != B_OK
|
||||||
|| gDeviceManager->get_attr_uint16(node, B_DEVICE_ID, &deviceId,
|
|| gDeviceManager->get_attr_uint16(node, B_DEVICE_ID, &deviceId,
|
||||||
@@ -857,17 +229,14 @@ init_device(device_node* node, void** device_cookie)
|
|||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
static void
|
uninit_device_pci(SdhciDevice* context, device_node* pciParent)
|
||||||
uninit_device(void* device_cookie)
|
|
||||||
{
|
{
|
||||||
// Get the PCI driver and device
|
// Get the PCI driver and device
|
||||||
pci_device_module_info* pci;
|
pci_device_module_info* pci;
|
||||||
pci_device* device;
|
pci_device* device;
|
||||||
uint16 vendorId, deviceId;
|
uint16 vendorId, deviceId;
|
||||||
|
|
||||||
SdhciDevice* context = (SdhciDevice*)device_cookie;
|
|
||||||
device_node* pciParent = gDeviceManager->get_parent_node(context->fNode);
|
|
||||||
gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci,
|
gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci,
|
||||||
(void**)&device);
|
(void**)&device);
|
||||||
|
|
||||||
@@ -882,43 +251,14 @@ uninit_device(void* device_cookie)
|
|||||||
context->fRicohOriginalMode);
|
context->fRicohOriginalMode);
|
||||||
pci->write_pci_config(device, SDHCI_PCI_RICOH_MODE_KEY, 1, 0);
|
pci->write_pci_config(device, SDHCI_PCI_RICOH_MODE_KEY, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
gDeviceManager->put_node(pciParent);
|
|
||||||
|
|
||||||
delete context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float
|
||||||
static status_t
|
supports_device_pci(device_node* parent)
|
||||||
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_PCI_DEVICE_MODULE_NAME,
|
|
||||||
attrs, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static float
|
|
||||||
supports_device(device_node* parent)
|
|
||||||
{
|
|
||||||
const char* bus;
|
|
||||||
uint16 type, subType;
|
uint16 type, subType;
|
||||||
uint16 vendorId, deviceId;
|
uint16 vendorId, deviceId;
|
||||||
|
|
||||||
// make sure parent is a PCI SDHCI device node
|
|
||||||
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 0.0f;
|
|
||||||
|
|
||||||
if (gDeviceManager->get_attr_uint16(parent, B_DEVICE_VENDOR_ID, &vendorId,
|
if (gDeviceManager->get_attr_uint16(parent, B_DEVICE_VENDOR_ID, &vendorId,
|
||||||
false) != B_OK
|
false) != B_OK
|
||||||
|| gDeviceManager->get_attr_uint16(parent, B_DEVICE_ID, &deviceId,
|
|| gDeviceManager->get_attr_uint16(parent, B_DEVICE_ID, &deviceId,
|
||||||
@@ -961,109 +301,22 @@ supports_device(device_node* parent)
|
|||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
set_clock(void* controller, uint32_t kilohertz)
|
|
||||||
{
|
|
||||||
SdhciBus* bus = (SdhciBus*)controller;
|
|
||||||
bus->SetClock(kilohertz);
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
do_io(void* controller, uint8_t command, IOOperation* operation,
|
|
||||||
bool offsetAsSectors)
|
|
||||||
{
|
|
||||||
CALLED();
|
|
||||||
|
|
||||||
SdhciBus* bus = (SdhciBus*)controller;
|
|
||||||
return bus->DoIO(command, operation, offsetAsSectors);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
set_scan_semaphore(void* controller, sem_id sem)
|
|
||||||
{
|
|
||||||
CALLED();
|
|
||||||
|
|
||||||
SdhciBus* bus = (SdhciBus*)controller;
|
|
||||||
return bus->SetScanSemaphore(sem);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
set_bus_width(void* controller, int width)
|
|
||||||
{
|
|
||||||
CALLED();
|
|
||||||
|
|
||||||
SdhciBus* bus = (SdhciBus*)controller;
|
|
||||||
return bus->SetBusWidth(width);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module_dependency module_dependencies[] = {
|
|
||||||
{ MMC_BUS_MODULE_NAME, (module_info**)&gMMCBusController},
|
|
||||||
{ B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager },
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Device node registered for each SD slot. It implements the MMC operations so
|
// Device node registered for each SD slot. It implements the MMC operations so
|
||||||
// the bus manager can use it to communicate with SD cards.
|
// the bus manager can use it to communicate with SD cards.
|
||||||
static mmc_bus_interface gSDHCIPCIDeviceModule = {
|
mmc_bus_interface gSDHCIPCIDeviceModule = {
|
||||||
{
|
.info = {
|
||||||
{
|
.info = {
|
||||||
SDHCI_PCI_MMC_BUS_MODULE_NAME,
|
.name = SDHCI_PCI_MMC_BUS_MODULE_NAME,
|
||||||
0,
|
|
||||||
NULL
|
|
||||||
},
|
},
|
||||||
NULL, // supports device
|
|
||||||
NULL, // register device
|
.init_driver = init_bus_pci,
|
||||||
init_bus,
|
.uninit_driver = uninit_bus,
|
||||||
uninit_bus,
|
.device_removed = bus_removed,
|
||||||
NULL, // register child devices
|
|
||||||
NULL, // rescan
|
|
||||||
bus_removed,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
set_clock,
|
.set_clock = set_clock,
|
||||||
execute_command,
|
.execute_command = execute_command,
|
||||||
do_io,
|
.do_io = do_io,
|
||||||
set_scan_semaphore,
|
.set_scan_semaphore = set_scan_semaphore,
|
||||||
set_bus_width
|
.set_bus_width = set_bus_width,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Root device that binds to the PCI bus. It will register an mmc_bus_interface
|
|
||||||
// node for each SD slot in the device.
|
|
||||||
static driver_module_info sSDHCIDevice = {
|
|
||||||
{
|
|
||||||
SDHCI_PCI_DEVICE_MODULE_NAME,
|
|
||||||
0,
|
|
||||||
NULL
|
|
||||||
},
|
|
||||||
supports_device,
|
|
||||||
register_device,
|
|
||||||
init_device,
|
|
||||||
uninit_device,
|
|
||||||
register_child_devices,
|
|
||||||
NULL, // rescan
|
|
||||||
NULL, // device removed
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
module_info* modules[] = {
|
|
||||||
(module_info* )&sSDHCIDevice,
|
|
||||||
(module_info* )&gSDHCIPCIDeviceModule,
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
@@ -1770,6 +1770,7 @@ device_node::_GetNextDriverPath(void*& cookie, KPath& _path)
|
|||||||
_AddPath(*stack, "busses/virtio");
|
_AddPath(*stack, "busses/virtio");
|
||||||
_AddPath(*stack, "bus_managers/pci");
|
_AddPath(*stack, "bus_managers/pci");
|
||||||
_AddPath(*stack, "busses/pci");
|
_AddPath(*stack, "busses/pci");
|
||||||
|
_AddPath(*stack, "busses/mmc");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user