/* * Copyright 2018-2019 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * B Krishnan Iyer, krishnaniyer97@gmail.com */ #include <new> #include <stdio.h> #include <string.h> #include <bus/PCI.h> #include <PCI_x86.h> #include <KernelExport.h> #include "sdhci_pci.h" #define TRACE_SDHCI #ifdef TRACE_SDHCI # define TRACE(x...) dprintf("\33[33msdhci_pci:\33[0m " x) #else # define TRACE(x...) ; #endif #define TRACE_ALWAYS(x...) dprintf("\33[33msdhci_pci:\33[0m " x) #define ERROR(x...) dprintf("\33[33msdhci_pci:\33[0m " x) #define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__) #define SDHCI_PCI_DEVICE_MODULE_NAME "busses/mmc/sdhci_pci/driver_v1" #define SDHCI_PCI_MMC_BUS_MODULE_NAME "busses/mmc/sdhci_pci/device/v1" #define SLOTS_COUNT "device/slots_count" #define SLOT_NUMBER "device/slot" #define BAR_INDEX "device/bar" typedef struct { struct registers* fRegisters; uint8_t fIrq; } sdhci_pci_mmc_bus_info; device_manager_info* gDeviceManager; device_module_info* gSDHCIDeviceController; static pci_x86_module_info* sPCIx86Module; static void sdhci_register_dump(uint8_t slot, struct registers* regs) { #ifdef TRACE_SDHCI TRACE("Register values for slot %d:\n", slot); TRACE("system_address: %d\n", regs->system_address); TRACE("%d blocks of size %d\n", regs->block_count, regs->block_size); TRACE("argument: %d\n", regs->argument); TRACE("transfer_mode: %d\n", regs->transfer_mode); TRACE("command: %d\n", regs->command.Bits()); TRACE("response:"); for (int i = 0; i < 8; i++) dprintf(" %d", regs->response[i]); dprintf("\n"); TRACE("buffer_data_port: %d\n", regs->buffer_data_port); TRACE("present_state: %x\n", regs->present_state.Bits()); TRACE("power_control: %d\n", regs->power_control.Bits()); TRACE("host_control: %d\n", regs->host_control); TRACE("wakeup_control: %d\n", regs->wakeup_control); TRACE("block_gap_control: %d\n", regs->block_gap_control); TRACE("clock_control: %x\n", regs->clock_control.Bits()); TRACE("software_reset: %d\n", regs->software_reset.Bits()); TRACE("timeout_control: %d\n", regs->timeout_control); TRACE("interrupt_status: %x enable: %x signal: %x\n", regs->interrupt_status, regs->interrupt_status_enable, regs->interrupt_signal_enable); TRACE("auto_cmd12_error_status: %d\n", regs->auto_cmd12_error_status); TRACE("capabilities: %lld\n", regs->capabilities.Bits()); TRACE("max_current_capabilities: %lld\n", regs->max_current_capabilities); TRACE("slot_interrupt_status: %d\n", regs->slot_interrupt_status); TRACE("host_controller_version spec %x vendor %x\n", regs->host_controller_version.specVersion, regs->host_controller_version.vendorVersion); #endif } static void sdhci_reset(struct registers* regs) { // if card is not present then no point of reseting the registers if (!regs->present_state.IsCardInserted()) return; // enabling software reset all regs->software_reset.ResetAll(); } static void sdhci_set_clock(struct registers* regs) { int base_clock = regs->capabilities.BaseClockFrequency(); // Try to get as close to 400kHz as possible, but not faster int divider = base_clock * 1000 / 400; if (regs->host_controller_version.specVersion <= 1) { // Old controller only support power of two dividers up to 256, // round to next power of two up to 256 if (divider > 256) divider = 256; divider--; divider |= divider >> 1; divider |= divider >> 2; divider |= divider >> 4; divider++; } divider = regs->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. regs->clock_control.EnableInternal(); // wait until internal clock is stabilized while (!(regs->clock_control.InternalStable())); regs->clock_control.EnablePLL(); while (!(regs->clock_control.InternalStable())); // Finally, route the clock to the SD card regs->clock_control.EnableSD(); } static void sdhci_stop_clock(struct registers* regs) { regs->clock_control.DisableSD(); } static void sdhci_set_power(struct registers* _regs) { uint8_t supportedVoltages = _regs->capabilities.SupportedVoltages(); if ((supportedVoltages & Capabilities::k3v3) != 0) _regs->power_control.SetVoltage(PowerControl::k3v3); else if ((supportedVoltages & Capabilities::k3v0) != 0) _regs->power_control.SetVoltage(PowerControl::k3v0); else if ((supportedVoltages & Capabilities::k1v8) != 0) _regs->power_control.SetVoltage(PowerControl::k1v8); else { _regs->power_control.PowerOff(); ERROR("No voltage is supported\n"); return; } if (!_regs->present_state.IsCardInserted()) { TRACE("Card not inserted\n"); return; } TRACE("Execute CMD0\n"); _regs->command.SendCommand(0, false); DELAY(1000); } static status_t init_bus(device_node* node, void** bus_cookie) { CALLED(); status_t status = B_OK; area_id regs_area; volatile uint32_t* regs; uint8_t bar, slot; sdhci_pci_mmc_bus_info* bus = new(std::nothrow) sdhci_pci_mmc_bus_info; if (bus == NULL) return B_NO_MEMORY; pci_device_module_info* pci; pci_device* device; device_node* parent = gDeviceManager->get_parent_node(node); device_node* pciParent = gDeviceManager->get_parent_node(parent); gDeviceManager->get_driver(pciParent, (driver_module_info**)&pci, (void**)&device); gDeviceManager->put_node(pciParent); gDeviceManager->put_node(parent); if (get_module(B_PCI_X86_MODULE_NAME, (module_info**)&sPCIx86Module) != B_OK) { sPCIx86Module = NULL; TRACE("PCIx86Module not loaded\n"); } if (gDeviceManager->get_attr_uint8(node, SLOT_NUMBER, &slot, false) < B_OK || gDeviceManager->get_attr_uint8(node, BAR_INDEX, &bar, false) < B_OK) return -1; TRACE("Register SD bus at slot %d, using bar %d\n", slot + 1, bar); pci_info pciInfo; pci->get_pci_info(device, &pciInfo); int msiCount = sPCIx86Module->get_msi_count(pciInfo.bus, pciInfo.device, pciInfo.function); TRACE("interrupts count: %d\n",msiCount); // enable bus master and io uint16 pcicmd = pci->read_pci_config(device, PCI_command, 2); pcicmd &= ~(PCI_command_int_disable | PCI_command_io); pcicmd |= PCI_command_master | PCI_command_memory; pci->write_pci_config(device, PCI_command, 2, pcicmd); TRACE("init_bus() %p node %p pci %p device %p\n", bus, node, pci, device); // mapping the registers by MMUIO method int bar_size = pciInfo->u.h0.base_register_sizes[bar]; regs_area = map_physical_memory("sdhc_regs_map", pciInfo.u.h0.base_registers[bar], pciInfo.u.h0.base_register_sizes[bar], B_ANY_KERNEL_BLOCK_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)®s); if (regs_area < B_OK) { TRACE("mapping failed"); return B_BAD_VALUE; } struct registers* _regs = (struct registers*)regs; bus->fRegisters = _regs; sdhci_reset(_regs); // 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. bus->fIrq = pciInfo.u.h0.interrupt_line; TRACE("irq interrupt line: %d\n", bus->fIrq); if (bus->fIrq == 0 || bus->fIrq == 0xff) { TRACE("PCI IRQ not assigned\n"); if (sPCIx86Module != NULL) { put_module(B_PCI_X86_MODULE_NAME); sPCIx86Module = NULL; } delete_area(regs_area); delete bus; return B_ERROR; } status = install_io_interrupt_handler(bus->fIrq, sdhci_generic_interrupt, bus, 0); if (status != B_OK) { TRACE("can't install interrupt handler\n"); if (sPCIx86Module != NULL) { put_module(B_PCI_X86_MODULE_NAME); sPCIx86Module = NULL; } delete_area(regs_area); delete bus; return status; } TRACE("interrupt handler installed\n"); _regs->interrupt_status_enable = SDHCI_INT_CMD_CMP | SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM | SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_INDEX | SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT; _regs->interrupt_signal_enable = SDHCI_INT_CMD_CMP | SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM | SDHCI_INT_TIMEOUT | SDHCI_INT_CRC | SDHCI_INT_INDEX | SDHCI_INT_BUS_POWER | SDHCI_INT_END_BIT; sdhci_register_dump(slot, _regs); sdhci_set_clock(_regs); sdhci_set_power(_regs); *bus_cookie = bus; return status; } static void uninit_bus(void* bus_cookie) { sdhci_pci_mmc_bus_info* bus = (sdhci_pci_mmc_bus_info*)bus_cookie; bus->fRegisters->interrupt_signal_enable = 0; bus->fRegisters->interrupt_status_enable = 0; remove_io_interrupt_handler(bus->fIrq, sdhci_generic_interrupt, bus); area_id regs_area = area_for(bus->fRegisters); delete_area(regs_area); // FIXME do we need to put() the PCI module here? delete bus; } void sdhci_error_interrupt_recovery(struct registers* _regs) { _regs->interrupt_signal_enable &= ~(SDHCI_INT_CMD_CMP | SDHCI_INT_TRANS_CMP | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM); if (_regs->interrupt_status & 7) _regs->software_reset.ResetTransaction(); int16_t erorr_status = _regs->interrupt_status; _regs->interrupt_status &= ~(erorr_status); } int32 sdhci_generic_interrupt(void* data) { sdhci_pci_mmc_bus_info* bus = (sdhci_pci_mmc_bus_info*)data; uint32_t intmask = bus->fRegisters->slot_interrupt_status; if ((intmask == 0) || (intmask == 0xffffffff)) { return B_UNHANDLED_INTERRUPT; } TRACE("interrupt function called\n"); // handling card presence interrupt if (intmask & (SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM)) { uint32_t card_present = ((intmask & SDHCI_INT_CARD_INS) != 0); bus->fRegisters->interrupt_status_enable &= ~(SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM); bus->fRegisters->interrupt_signal_enable &= ~(SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM); bus->fRegisters->interrupt_status_enable |= card_present ? SDHCI_INT_CARD_REM : SDHCI_INT_CARD_INS; bus->fRegisters->interrupt_signal_enable |= card_present ? SDHCI_INT_CARD_REM : SDHCI_INT_CARD_INS; bus->fRegisters->interrupt_status |= (intmask & (SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM)); TRACE("Card presence interrupt handled\n"); return B_HANDLED_INTERRUPT; } // handling command interrupt if (intmask & SDHCI_INT_CMD_MASK) { bus->fRegisters->interrupt_status |= (intmask & SDHCI_INT_CMD_MASK); // TODO do something with the interrupt TRACE("Command interrupt handled\n"); return B_HANDLED_INTERRUPT; } // handling bus power interrupt if (intmask & SDHCI_INT_BUS_POWER) { bus->fRegisters->interrupt_status |= SDHCI_INT_BUS_POWER; TRACE("card is consuming too much power\n"); return B_HANDLED_INTERRUPT; } intmask = bus->fRegisters->slot_interrupt_status; if (intmask != 0) { ERROR("Remaining interrupts at end of handler: %x\n", intmask); intmask &= ~(SDHCI_INT_BUS_POWER | SDHCI_INT_CARD_INS | SDHCI_INT_CARD_REM | SDHCI_INT_CMD_MASK); } return B_UNHANDLED_INTERRUPT; } static void bus_removed(void* bus_cookie) { return; } static status_t register_child_devices(void* cookie) { CALLED(); device_node* node = (device_node*)cookie; device_node* parent = gDeviceManager->get_parent_node(node); pci_device_module_info* pci; pci_device* device; uint8 slots_count, bar, slotsInfo; gDeviceManager->get_driver(parent, (driver_module_info**)&pci, (void**)&device); uint16 pciSubDeviceId = pci->read_pci_config(device, PCI_subsystem_id, 2); slotsInfo = pci->read_pci_config(device, SDHCI_PCI_SLOT_INFO, 1); bar = SDHCI_PCI_SLOT_INFO_FIRST_BASE_INDEX(slotsInfo); slots_count = SDHCI_PCI_SLOTS(slotsInfo); char prettyName[25]; if (slots_count > 6 || bar > 5) { TRACE("Invalid slots count: %d or BAR count: %d \n", slots_count, bar); return B_BAD_VALUE; } for (uint8_t slot = 0; slot <= slots_count; slot++) { bar = bar + slot; sprintf(prettyName, "SDHC bus %" B_PRIu16 " slot %" B_PRIu8, pciSubDeviceId, slot); device_attr attrs[] = { // properties of this controller for SDHCI bus manager { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { string: prettyName }}, { B_DEVICE_FIXED_CHILD, B_STRING_TYPE, {string: SDHCI_BUS_CONTROLLER_MODULE_NAME}}, {SDHCI_DEVICE_TYPE_ITEM, B_UINT16_TYPE, { ui16: pciSubDeviceId}}, {B_DEVICE_BUS, B_STRING_TYPE,{string: "mmc"}}, {SLOT_NUMBER, B_UINT8_TYPE, { ui8: slot}}, {BAR_INDEX, B_UINT8_TYPE, { ui8: bar}}, { NULL } }; if (gDeviceManager->register_node(node, SDHCI_PCI_MMC_BUS_MODULE_NAME, attrs, NULL, &node) != B_OK) return B_BAD_VALUE; } return B_OK; } static status_t init_device(device_node* node, void** device_cookie) { CALLED(); *device_cookie = node; return B_OK; } 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_PCI_DEVICE_MODULE_NAME, attrs, NULL, NULL); } static float supports_device(device_node* parent) { CALLED(); const char* bus; uint16 type, subType; uint8 pciSubDeviceId; // make sure parent is a PCI SDHCI device node if (gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false) != B_OK || gDeviceManager->get_attr_uint16(parent, B_DEVICE_SUB_TYPE, &subType, false) < B_OK || gDeviceManager->get_attr_uint16(parent, B_DEVICE_TYPE, &type, false) < B_OK) return -1; if (strcmp(bus, "pci") != 0) return 0.0f; if (type == PCI_base_peripheral) { if (subType != PCI_sd_host) return 0.0f; pci_device_module_info* pci; pci_device* device; gDeviceManager->get_driver(parent, (driver_module_info**)&pci, (void**)&device); pciSubDeviceId = pci->read_pci_config(device, PCI_revision, 1); TRACE("SDHCI Device found! Subtype: 0x%04x, type: 0x%04x\n", subType, type); return 0.8f; } return 0.0f; } module_dependency module_dependencies[] = { { SDHCI_BUS_CONTROLLER_MODULE_NAME, (module_info**)&gSDHCIDeviceController}, { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&gDeviceManager }, {} }; static sdhci_mmc_bus_interface gSDHCIPCIDeviceModule = { { { SDHCI_PCI_MMC_BUS_MODULE_NAME, 0, NULL }, NULL, // supports device NULL, // register device init_bus, uninit_bus, NULL, // register child devices NULL, // rescan bus_removed, } }; static driver_module_info sSDHCIDevice = { { SDHCI_PCI_DEVICE_MODULE_NAME, 0, NULL }, supports_device, register_device, init_device, NULL, // uninit register_child_devices, NULL, // rescan NULL, // device removed }; module_info* modules[] = { (module_info* )&sSDHCIDevice, (module_info* )&gSDHCIPCIDeviceModule, NULL };