| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "msm8x53-power.h" |
| |
| #include <lib/device-protocol/pdev.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/platform-defs.h> |
| #include <ddk/protocol/platform/bus.h> |
| #include <ddk/protocol/platform/device.h> |
| #include <fbl/unique_ptr.h> |
| #include <soc/msm8x53/msm8x53-power-regs.h> |
| #include <soc/msm8x53/msm8x53-power.h> |
| |
| namespace power { |
| |
| constexpr Msm8x53PowerDomainInfo kMsm8x53PowerDomains[] = { |
| [kVRegS1] = {.type = RPM_REGULATOR}, [kVRegS2] = {.type = RPM_REGULATOR}, |
| [kVRegS3] = {.type = RPM_REGULATOR}, [kVRegS4] = {.type = RPM_REGULATOR}, |
| [kVRegS5] = {.type = RPM_REGULATOR}, [kVRegS6] = {.type = RPM_REGULATOR}, |
| [kVRegS7] = {.type = RPM_REGULATOR}, [kVRegLdoA1] = {.type = RPM_REGULATOR}, |
| [kVRegLdoA2] = {.type = RPM_REGULATOR}, [kVRegLdoA3] = {.type = RPM_REGULATOR}, |
| [kVRegLdoA5] = {.type = RPM_REGULATOR}, [kVRegLdoA6] = {.type = RPM_REGULATOR}, |
| [kVRegLdoA7] = {.type = RPM_REGULATOR}, [kVRegLdoA8] = {.type = RPM_REGULATOR}, |
| [kVRegLdoA9] = {.type = RPM_REGULATOR}, [kVRegLdoA10] = {.type = RPM_REGULATOR}, |
| [kVRegLdoA11] = {.type = RPM_REGULATOR}, [kVRegLdoA12] = {.type = RPM_REGULATOR}, |
| [kVRegLdoA13] = {.type = RPM_REGULATOR}, [kVRegLdoA16] = {.type = RPM_REGULATOR}, |
| [kVRegLdoA17] = {.type = RPM_REGULATOR}, [kVRegLdoA19] = {.type = RPM_REGULATOR}, |
| [kVRegLdoA22] = {.type = RPM_REGULATOR}, [kVRegLdoA23] = {.type = RPM_REGULATOR}, |
| [kPmicCtrlReg] = {.type = PMIC_CTRL_REGISTER}, |
| }; |
| |
| zx_status_t Msm8x53Power::ReadPMICReg(uint32_t reg_addr, uint32_t* reg_value) { |
| // Extract slave id, periph id and register offset. |
| auto reg = PmicRegAddr::Get().FromValue(reg_addr); |
| uint32_t reg_offset = reg.reg_offset(); |
| uint32_t periph_id = reg.periph_id(); |
| uint32_t slave_id = reg.slave_id(); |
| |
| uint32_t ppid = PPID(slave_id, periph_id); |
| // SID is 4 bits and PPID is 8 bits and ppid will always < 4096(kMaxPPIDEntries) |
| // So there is not need for bounds check. |
| ZX_DEBUG_ASSERT(ppid < kMaxPPIDEntries); |
| |
| uint32_t apid = ppid_to_apid_[ppid]; |
| // Disable Irq mode for the current channel. |
| // TODO(ravoorir): Support Interrupts. |
| uint32_t cmd_cfg_offset = PMIC_ARB_CHANNEL_CMD_CONFIG_OFFSET(apid); |
| PmicArbCoreChannelCmdConfig::Get(cmd_cfg_offset) |
| .ReadFrom(&obsvr_mmio_) |
| .set_intr(0) |
| .WriteTo(&obsvr_mmio_); |
| // Write the CMD to read data |
| // TODO(ravoorir): Update byte_cnt with actual number of bytes |
| uint32_t cmd_offset = PMIC_ARB_CHANNEL_CMD_OFFSET(apid); |
| PmicArbCoreChannelCmdInfo::Get(cmd_offset) |
| .ReadFrom(&obsvr_mmio_) |
| .set_byte_cnt(0) |
| .set_reg_offset_addr(reg_offset) |
| .set_periph_id(periph_id) |
| .set_slave_id(slave_id) |
| .set_priority(0) |
| .set_opcode(kSpmiCmdRegReadOpcode) |
| .WriteTo(&obsvr_mmio_); |
| // Wait for CMD Completion |
| uint32_t status = 0; |
| while (!status) { |
| status = PmicArbCoreChannelCmdStatus::Get(PMIC_ARB_CHANNEL_CMD_STATUS_OFFSET(apid)) |
| .ReadFrom(&obsvr_mmio_) |
| .status(); |
| } |
| if (status ^ PmicArbCoreChannelCmdStatus::kPmicArbCmdDone) { |
| // Cmd completed with an error |
| zxlogf(ERROR, "%s Unable to read Pmic Reg: 0x%x status: 0x%x\n", __FUNCTION__, reg_addr, |
| status); |
| return ZX_ERR_IO; |
| } |
| |
| // Read RDATA0 |
| uint32_t rdata = PmicArbCoreChannelCmdRData::Get(PMIC_ARB_CHANNEL_CMD_RDATA0_OFFSET(apid)) |
| .ReadFrom(&obsvr_mmio_) |
| .data(); |
| *reg_value = rdata; |
| return ZX_OK; |
| } |
| |
| zx_status_t Msm8x53Power::WritePMICReg(uint32_t reg_addr, uint32_t value) { |
| // Extract slave id, periph id and register offset. |
| auto reg = PmicRegAddr::Get().FromValue(reg_addr); |
| uint32_t reg_offset = reg.reg_offset(); |
| uint32_t periph_id = reg.periph_id(); |
| uint32_t slave_id = reg.slave_id(); |
| |
| uint32_t ppid = PPID(slave_id, periph_id); |
| |
| // SID is 4 bits and PPID is 8 bits and ppid will always < 4096(kMaxPPIDEntries) |
| // So there is not need for bounds check. |
| ZX_DEBUG_ASSERT(ppid < kMaxPPIDEntries); |
| |
| uint32_t apid = ppid_to_apid_[ppid]; |
| |
| // Disable Irq mode for the current channel. |
| // TODO(ravoorir): Support Interrupts later |
| uint32_t cmd_cfg_offset = PMIC_ARB_CHANNEL_CMD_CONFIG_OFFSET(apid); |
| PmicArbCoreChannelCmdConfig::Get(cmd_cfg_offset) |
| .ReadFrom(&chnls_mmio_) |
| .set_intr(0) |
| .WriteTo(&chnls_mmio_); |
| |
| // Write first 4 bytes to WDATA0 |
| // TODO(ravoorir): Support writing of 8 byte data. |
| PmicArbCoreChannelCmdWData::Get(PMIC_ARB_CHANNEL_CMD_WDATA0_OFFSET(apid)) |
| .ReadFrom(&chnls_mmio_) |
| .set_data(value) |
| .WriteTo(&chnls_mmio_); |
| |
| // Write the CMD |
| // TODO(ravoorir): update byte_cnt to the write byte count. |
| uint32_t cmd_offset = PMIC_ARB_CHANNEL_CMD_OFFSET(apid); |
| PmicArbCoreChannelCmdInfo::Get(cmd_offset) |
| .ReadFrom(&chnls_mmio_) |
| .set_byte_cnt(0) |
| .set_reg_offset_addr(reg_offset) |
| .set_periph_id(periph_id) |
| .set_slave_id(slave_id) |
| .set_priority(0) |
| .set_opcode(kSpmiCmdRegWriteOpcode) |
| .WriteTo(&chnls_mmio_); |
| |
| // Wait for CMD Completion |
| uint32_t status = 0; |
| while (!status) { |
| status = PmicArbCoreChannelCmdStatus::Get(PMIC_ARB_CHANNEL_CMD_STATUS_OFFSET(apid)) |
| .ReadFrom(&chnls_mmio_) |
| .status(); |
| } |
| if (status ^ PmicArbCoreChannelCmdStatus::kPmicArbCmdDone) { |
| // Cmd completed with an error |
| zxlogf(ERROR, "%s Unable to write PMIC Reg 0x%x status:0x%x\n", __FUNCTION__, reg_addr, status); |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Msm8x53Power::RpmRegulatorEnable(const Msm8x53PowerDomainInfo* domain) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Msm8x53Power::RpmRegulatorDisable(const Msm8x53PowerDomainInfo* domain) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Msm8x53Power::SpmRegulatorEnable(const Msm8x53PowerDomainInfo* domain) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Msm8x53Power::SpmRegulatorDisable(const Msm8x53PowerDomainInfo* domain) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Msm8x53Power::PowerImplWritePmicCtrlReg(uint32_t index, uint32_t addr, uint32_t value) { |
| if (index != kPmicCtrlReg) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return WritePMICReg(addr, value); |
| } |
| |
| zx_status_t Msm8x53Power::PowerImplReadPmicCtrlReg(uint32_t index, uint32_t addr, uint32_t* value) { |
| if (index != kPmicCtrlReg) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ReadPMICReg(addr, value); |
| } |
| |
| zx_status_t Msm8x53Power::PowerImplDisablePowerDomain(uint32_t index) { |
| if (index >= fbl::count_of(kMsm8x53PowerDomains)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| const Msm8x53PowerDomainInfo* domain = &kMsm8x53PowerDomains[index]; |
| if (domain->type == RPM_REGULATOR) { |
| return RpmRegulatorDisable(domain); |
| } else if (domain->type == SPM_REGULATOR) { |
| return SpmRegulatorDisable(domain); |
| } |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t Msm8x53Power::PowerImplEnablePowerDomain(uint32_t index) { |
| if (index >= fbl::count_of(kMsm8x53PowerDomains)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| const Msm8x53PowerDomainInfo* domain = &kMsm8x53PowerDomains[index]; |
| if (domain->type == RPM_REGULATOR) { |
| return RpmRegulatorEnable(domain); |
| } else if (domain->type == SPM_REGULATOR) { |
| return SpmRegulatorEnable(domain); |
| } |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t Msm8x53Power::PowerImplGetPowerDomainStatus(uint32_t index, |
| power_domain_status_t* out_status) { |
| if (index >= fbl::count_of(kMsm8x53PowerDomains)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Msm8x53Power::PowerImplGetSupportedVoltageRange(uint32_t index, uint32_t* min_voltage, |
| uint32_t* max_voltage) { |
| if (index >= fbl::count_of(kMsm8x53PowerDomains)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Msm8x53Power::PowerImplRequestVoltage(uint32_t index, uint32_t voltage, |
| uint32_t* actual_voltage) { |
| if (index >= fbl::count_of(kMsm8x53PowerDomains)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t Msm8x53Power::PowerImplGetCurrentVoltage(uint32_t index, uint32_t* current_voltage) { |
| if (index >= fbl::count_of(kMsm8x53PowerDomains)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void Msm8x53Power::DdkRelease() { delete this; } |
| |
| void Msm8x53Power::DdkUnbind() { DdkRemove(); } |
| |
| zx_status_t Msm8x53Power::PmicArbInit() { |
| // Read version |
| static uint32_t pmic_arb_ver = PmicArbVersion::Get().ReadFrom(&core_mmio_).arb_version(); |
| zxlogf(ERROR, "%s Pmic Arbiter version: 0x%x\n", __FUNCTION__, pmic_arb_ver); |
| if (pmic_arb_ver != kPmicArbVersionTwo) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| uint32_t slave_id = 0; |
| uint32_t periph_id = 0; |
| |
| // Maintain PPID->APID mapping |
| for (uint32_t apid = 0; apid < kMaxPmicPeripherals; apid++) { |
| uint32_t core_channel_offset = PMIC_ARB_CORE_CHANNEL_INFO_OFFSET(apid); |
| auto reg = PmicArbCoreChannelInfo::Get(core_channel_offset).ReadFrom(&core_mmio_); |
| slave_id = reg.slave_id(); |
| periph_id = reg.periph_id(); |
| ppid_to_apid_[PPID(slave_id, periph_id)] = apid; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Msm8x53Power::Init() { |
| PmicArbInit(); |
| return ZX_OK; |
| } |
| |
| zx_status_t Msm8x53Power::Create(void* ctx, zx_device_t* parent) { |
| zx_status_t status = ZX_OK; |
| |
| ddk::PDev pdev(parent); |
| if (!pdev.is_valid()) { |
| zxlogf(ERROR, "%s Could not get pdev: %d\n", __FUNCTION__, status); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| std::optional<ddk::MmioBuffer> core_mmio; |
| status = pdev.MapMmio(kPmicArbCoreMmioIndex, &core_mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s Failed to get core mmio: %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> chnls_mmio; |
| status = pdev.MapMmio(kPmicArbChnlsMmioIndex, &chnls_mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s Failed to get core mmio: %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> obsvr_mmio; |
| status = pdev.MapMmio(kPmicArbObsrvrMmioIndex, &obsvr_mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s Failed to get core mmio: %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> intr_mmio; |
| status = pdev.MapMmio(kPmicArbIntrMmioIndex, &intr_mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s Failed to get core mmio: %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> cfg_mmio; |
| status = pdev.MapMmio(kPmicArbCnfgMmioIndex, &cfg_mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s Failed to get core mmio: %d\n", __FUNCTION__, status); |
| return status; |
| } |
| |
| auto dev = std::make_unique<Msm8x53Power>(parent, *std::move(core_mmio), *std::move(chnls_mmio), |
| *std::move(obsvr_mmio), *std::move(intr_mmio), |
| *std::move(cfg_mmio)); |
| |
| if ((status = dev->Init()) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = dev->DdkAdd("msm8x53-power")) != ZX_OK) { |
| return status; |
| } |
| |
| // devmgr is now in charge of the device. |
| __UNUSED auto* dummy = dev.release(); |
| return ZX_OK; |
| } |
| |
| static constexpr zx_driver_ops_t msm8x53_power_driver_ops = []() { |
| zx_driver_ops_t driver_ops = {}; |
| driver_ops.version = DRIVER_OPS_VERSION; |
| driver_ops.bind = Msm8x53Power::Create; |
| return driver_ops; |
| }(); |
| |
| } // namespace power |
| |
| ZIRCON_DRIVER_BEGIN(msm8x53_power, power::msm8x53_power_driver_ops, "zircon", "0.1", 3) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV), |
| BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_QUALCOMM), |
| BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_QUALCOMM_POWER), |
| ZIRCON_DRIVER_END(msm8x53_power) |