blob: 5d40afa7411575c26a119453fd906723027362d9 [file] [log] [blame]
// 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 "aml-pwm.h"
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/metadata.h>
#include <lib/device-protocol/pdev-fidl.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <limits>
#include <soc/aml-a1/a1-pwm.h>
#include <soc/aml-a113/a113-pwm.h>
#include <soc/aml-a5/a5-pwm.h>
#include <soc/aml-common/aml-pwm-regs.h>
#include <soc/aml-s905d2/s905d2-pwm.h>
#include <soc/aml-t931/t931-pwm.h>
namespace pwm {
namespace {
// Input clock frequency
constexpr uint32_t kReferenceClockFrequencyHz = 24'000'000;
// Nanoseconds per second
constexpr uint32_t kNsecPerSec = 1'000'000'000;
constexpr int64_t DivideRounded(int64_t num, int64_t denom) { return (num + (denom / 2)) / denom; }
constexpr int kMinimumDivider = 1;
constexpr int kMaximumDivider = 0x7f + 1;
// Below explains how `kMaximumAllowedPeriodNs` is calculated and we statically
// verify the value.
//
// High / low time of a PWM period is stored in registers as numbers of clock
// periods and dividers:
//
// High time (ns) = <NUMBER OF CLOCK PERIODS> (16 bits unsigned)
// * (Nanoseconds per clock period)
// * (<DIVIDER> + 1) (<DIVIDER> is 7-bit unsigned)
//
// So the maximum allowed PWM period is the maximum value of High (low) time.
//
// 0xffff * (Nanoseconds per clock period) * (0x7f + 1) (unit: ns)
static_assert([] {
constexpr int64_t kNanosecondsPerClock = kNsecPerSec / kReferenceClockFrequencyHz;
constexpr int kMaxClockPeriodCount = std::numeric_limits<uint16_t>::max();
return kMaximumAllowedPeriodNs == kNanosecondsPerClock * kMaximumDivider * kMaxClockPeriodCount;
}());
// Gets the minimum allowed divider value from given `period_ns`.
//
// `period_ns` must be not greater than kMaximumAllowedPeriodNs.
int GetDividerFromPeriodNs(int64_t period_ns) {
ZX_ASSERT(period_ns <= kMaximumAllowedPeriodNs);
constexpr int64_t kNanosecondsPerClock = kNsecPerSec / kReferenceClockFrequencyHz;
for (int divider = kMinimumDivider; divider <= kMaximumDivider; divider++) {
const int64_t period_count = DivideRounded(period_ns, kNanosecondsPerClock * divider);
if (period_count <= std::numeric_limits<uint16_t>::max()) {
return divider;
}
}
// Given that period_ns never exceeds the maximum allowed value, this should
// never happen.
ZX_ASSERT(false);
}
// Gets the minimum allowed divider value from given `config`, fulfilling
// requirements for all the timers.
//
// `period_ns` and `period_ns2` (for two-timer mode) must be not greater than
// kMaximumAllowedPeriodNs.
int GetDividerFromConfig(const pwm_config_t* config) {
ZX_ASSERT(config);
ZX_ASSERT(config->mode_config_buffer);
const auto* mode_cfg = reinterpret_cast<const mode_config*>(config->mode_config_buffer);
switch (mode_cfg->mode) {
case Mode::kOff:
return 1;
case Mode::kOn:
case Mode::kDeltaSigma: {
ZX_ASSERT(config->period_ns <= kMaximumAllowedPeriodNs);
return GetDividerFromPeriodNs(config->period_ns);
}
case Mode::kTwoTimer: {
ZX_ASSERT(config->period_ns <= kMaximumAllowedPeriodNs);
ZX_ASSERT(mode_cfg->two_timer.period_ns2 <= kMaximumAllowedPeriodNs);
int divider_first_timer = GetDividerFromPeriodNs(config->period_ns);
int divider_second_timer = GetDividerFromPeriodNs(mode_cfg->two_timer.period_ns2);
return std::max(divider_first_timer, divider_second_timer);
}
}
}
struct DutyCycleClockCount {
int64_t high_count;
int64_t low_count;
};
DutyCycleClockCount DutyCycleToClockCount(int divider, float duty_cycle, int64_t period_ns) {
constexpr int64_t kNanosecondsPerClock = kNsecPerSec / kReferenceClockFrequencyHz;
// Calculate the high and low count first based on the duty cycle requested.
const int64_t high_time_ns = DivideRounded((duty_cycle * static_cast<float>(period_ns)), 100.0);
const int64_t period_count = DivideRounded(period_ns, kNanosecondsPerClock * divider);
const int64_t duty_count = DivideRounded(high_time_ns, kNanosecondsPerClock * divider);
int64_t high_count = duty_count;
int64_t low_count = period_count - duty_count;
if (duty_count != period_count && duty_count != 0) {
high_count--;
low_count--;
}
return DutyCycleClockCount{
.high_count = high_count,
.low_count = low_count,
};
}
bool IsValidConfig(const pwm_config_t* config) {
if (config == nullptr) {
zxlogf(ERROR, "config is null");
return false;
}
if (config->mode_config_buffer == nullptr) {
zxlogf(ERROR, "mode_config_buffer not found");
return false;
}
if (config->mode_config_size != sizeof(mode_config)) {
zxlogf(ERROR, "mode_config_size incorrect: expected %lu, actual %lu", sizeof(mode_config),
config->mode_config_size);
return false;
}
auto mode_cfg = reinterpret_cast<const mode_config*>(config->mode_config_buffer);
Mode mode = mode_cfg->mode;
switch (mode) {
case Mode::kOff:
return true;
case Mode::kTwoTimer:
if (!(mode_cfg->two_timer.duty_cycle2 >= 0.0f && mode_cfg->two_timer.duty_cycle2 <= 100.0f)) {
zxlogf(ERROR, "timer #2 duty cycle (%0.3f) is not in [0.0, 100.0]",
mode_cfg->two_timer.duty_cycle2);
return false;
}
if (mode_cfg->two_timer.period_ns2 > kMaximumAllowedPeriodNs) {
zxlogf(ERROR, "timer #2 period (%u ns) exceeds the maximum allowed period (%ld ns)",
mode_cfg->two_timer.period_ns2, kMaximumAllowedPeriodNs);
return false;
}
[[fallthrough]];
case Mode::kOn:
case Mode::kDeltaSigma:
if (!(config->duty_cycle >= 0.0f && config->duty_cycle <= 100.0f)) {
zxlogf(ERROR, "timer #1 duty cycle (%0.3f) is not in [0.0, 100.0]", config->duty_cycle);
return false;
}
if (config->period_ns > kMaximumAllowedPeriodNs) {
zxlogf(ERROR, "timer #1 period (%u ns) exceeds the maximum allowed period (%ld ns)",
config->period_ns, kMaximumAllowedPeriodNs);
return false;
}
break;
default:
zxlogf(ERROR, "Unsupported mode (%u)", static_cast<uint32_t>(mode));
return false;
}
return true;
}
void CopyConfig(pwm_config_t* dest, const pwm_config_t* src) {
ZX_DEBUG_ASSERT(dest->mode_config_buffer);
ZX_DEBUG_ASSERT(dest->mode_config_size == src->mode_config_size);
ZX_DEBUG_ASSERT(dest->mode_config_size == sizeof(mode_config));
dest->polarity = src->polarity;
dest->period_ns = src->period_ns;
dest->duty_cycle = src->duty_cycle;
memcpy(dest->mode_config_buffer, src->mode_config_buffer, src->mode_config_size);
}
} // namespace
zx_status_t AmlPwm::PwmImplGetConfig(uint32_t idx, pwm_config_t* out_config) {
if (idx > 1) {
return ZX_ERR_INVALID_ARGS;
}
if (out_config->mode_config_buffer == nullptr ||
out_config->mode_config_size != configs_[idx].mode_config_size ||
out_config->mode_config_size != sizeof(mode_config)) {
return ZX_ERR_INVALID_ARGS;
}
CopyConfig(out_config, &configs_[idx]);
return ZX_OK;
}
zx_status_t AmlPwm::PwmImplSetConfig(uint32_t idx, const pwm_config_t* config) {
if (idx > 1) {
return ZX_ERR_INVALID_ARGS;
}
if (!IsValidConfig(config)) {
return ZX_ERR_INVALID_ARGS;
}
// Save old config
mode_config tmp_cfg = {Mode::kOff, {}};
pwm_config_t old_config = {false, 0, 0.0, reinterpret_cast<uint8_t*>(&tmp_cfg),
sizeof(mode_config)};
CopyConfig(&old_config, &configs_[idx]);
auto old_mode_cfg = reinterpret_cast<const mode_config*>(old_config.mode_config_buffer);
// Update new
CopyConfig(&configs_[idx], config);
auto mode_cfg = reinterpret_cast<const mode_config*>(config->mode_config_buffer);
Mode mode = mode_cfg->mode;
bool mode_eq = (old_mode_cfg->mode == mode);
if (!mode_eq) {
SetMode(idx, mode);
}
if (mode == Mode::kOff) {
return ZX_OK;
}
int old_divider = GetDividerFromConfig(&old_config);
int new_divider = GetDividerFromConfig(config);
bool divider_eq = old_divider == new_divider;
if (!(mode_eq && divider_eq)) {
SetClockDivider(idx, new_divider);
}
bool en_const = (config->duty_cycle == 0 || config->duty_cycle == 100);
bool val_eq;
if (mode == Mode::kDeltaSigma) {
val_eq = (old_mode_cfg->delta_sigma.delta == mode_cfg->delta_sigma.delta);
if (!(mode_eq && val_eq)) {
SetDSSetting(idx, mode_cfg->delta_sigma.delta);
}
}
if (mode == Mode::kTwoTimer) {
en_const = (en_const || mode_cfg->two_timer.duty_cycle2 == 0 ||
mode_cfg->two_timer.duty_cycle2 == 100);
val_eq = (old_mode_cfg->two_timer.period_ns2 == mode_cfg->two_timer.period_ns2) &&
(old_mode_cfg->two_timer.duty_cycle2 == mode_cfg->two_timer.duty_cycle2);
if (!(mode_eq && divider_eq && val_eq)) {
SetDutyCycle2(idx, new_divider, mode_cfg->two_timer.period_ns2,
mode_cfg->two_timer.duty_cycle2);
}
val_eq = (old_mode_cfg->two_timer.timer1 == mode_cfg->two_timer.timer1) &&
(old_mode_cfg->two_timer.timer2 == mode_cfg->two_timer.timer2);
if (!(mode_eq && val_eq)) {
SetTimers(idx, mode_cfg->two_timer.timer1, mode_cfg->two_timer.timer2);
}
}
val_eq = (old_config.polarity == config->polarity);
if (!(mode_eq && val_eq)) {
Invert(idx, config->polarity);
}
EnableConst(idx, en_const);
val_eq =
(old_config.period_ns == config->period_ns) && (old_config.duty_cycle == config->duty_cycle);
if (!(mode_eq && divider_eq && val_eq)) {
SetDutyCycle(idx, new_divider, config->period_ns, config->duty_cycle);
}
return ZX_OK;
}
zx_status_t AmlPwm::PwmImplEnable(uint32_t idx) {
if (idx > 1) {
return ZX_ERR_INVALID_ARGS;
}
if (!enabled_[idx]) {
EnableClock(idx, true);
enabled_[idx] = true;
}
return ZX_OK;
}
zx_status_t AmlPwm::PwmImplDisable(uint32_t idx) {
if (idx > 1) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status = ZX_OK;
if (enabled_[idx]) {
EnableClock(idx, false);
enabled_[idx] = false;
}
return status;
}
void AmlPwm::SetMode(uint32_t idx, Mode mode) {
fbl::AutoLock lock(&locks_[REG_MISC]);
auto misc_reg = MiscReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
misc_reg.set_en_b(mode == Mode::kOn || mode == Mode::kTwoTimer)
.set_ds_en_b(mode == Mode::kDeltaSigma)
.set_en_b2(mode == Mode::kTwoTimer);
} else {
misc_reg.set_en_a(mode == Mode::kOn || mode == Mode::kTwoTimer)
.set_ds_en_a(mode == Mode::kDeltaSigma)
.set_en_a2(mode == Mode::kTwoTimer);
}
misc_reg.WriteTo(&mmio_);
}
void AmlPwm::SetDutyCycle(uint32_t idx, int divider, uint32_t period_ns, float duty_cycle) {
ZX_ASSERT(duty_cycle >= 0.0f);
ZX_ASSERT(duty_cycle <= 100.0f);
ZX_ASSERT(divider >= 1);
ZX_ASSERT(divider <= 128);
// Write duty cycle to registers
DutyCycleClockCount clock_count = DutyCycleToClockCount(divider, duty_cycle, period_ns);
if (clock_count.high_count < std::numeric_limits<uint16_t>::min() ||
clock_count.high_count > std::numeric_limits<uint16_t>::max()) {
zxlogf(ERROR, "Invalid high count: %ld", clock_count.high_count);
}
if (clock_count.low_count < std::numeric_limits<uint16_t>::min() ||
clock_count.low_count > std::numeric_limits<uint16_t>::max()) {
zxlogf(ERROR, "Invalid low count: %ld", clock_count.low_count);
}
if (idx % 2) {
fbl::AutoLock lock(&locks_[REG_B]);
DutyCycleReg::GetB()
.ReadFrom(&mmio_)
.set_high(clock_count.high_count)
.set_low(clock_count.low_count)
.WriteTo(&mmio_);
} else {
fbl::AutoLock lock(&locks_[REG_A]);
DutyCycleReg::GetA()
.ReadFrom(&mmio_)
.set_high(clock_count.high_count)
.set_low(clock_count.low_count)
.WriteTo(&mmio_);
}
}
void AmlPwm::SetDutyCycle2(uint32_t idx, int divider, uint32_t period_ns, float duty_cycle) {
ZX_ASSERT(duty_cycle >= 0.0f);
ZX_ASSERT(duty_cycle <= 100.0f);
ZX_ASSERT(divider >= 1);
ZX_ASSERT(divider <= 128);
// Write duty cycle to registers
DutyCycleClockCount clock_count = DutyCycleToClockCount(divider, duty_cycle, period_ns);
if (clock_count.high_count < std::numeric_limits<uint16_t>::min() ||
clock_count.high_count > std::numeric_limits<uint16_t>::max()) {
zxlogf(ERROR, "Invalid high count: %ld", clock_count.high_count);
}
if (clock_count.low_count < std::numeric_limits<uint16_t>::min() ||
clock_count.low_count > std::numeric_limits<uint16_t>::max()) {
zxlogf(ERROR, "Invalid low count: %ld", clock_count.low_count);
}
if (idx % 2) {
fbl::AutoLock lock(&locks_[REG_B2]);
DutyCycleReg::GetB2()
.ReadFrom(&mmio_)
.set_high(clock_count.high_count)
.set_low(clock_count.low_count)
.WriteTo(&mmio_);
} else {
fbl::AutoLock lock(&locks_[REG_A2]);
DutyCycleReg::GetA2()
.ReadFrom(&mmio_)
.set_high(clock_count.high_count)
.set_low(clock_count.low_count)
.WriteTo(&mmio_);
}
}
void AmlPwm::Invert(uint32_t idx, bool on) {
fbl::AutoLock lock(&locks_[REG_MISC]);
auto misc_reg = MiscReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
misc_reg.set_inv_en_b(on);
} else {
misc_reg.set_inv_en_a(on);
}
misc_reg.WriteTo(&mmio_);
}
void AmlPwm::EnableHiZ(uint32_t idx, bool on) {
fbl::AutoLock lock(&locks_[REG_MISC]);
auto misc_reg = MiscReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
misc_reg.set_hiz_b(on);
} else {
misc_reg.set_hiz_a(on);
}
misc_reg.WriteTo(&mmio_);
}
void AmlPwm::EnableClock(uint32_t idx, bool on) {
fbl::AutoLock lock(&locks_[REG_MISC]);
auto misc_reg = MiscReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
misc_reg.set_clk_en_b(on);
} else {
misc_reg.set_clk_en_a(on);
}
misc_reg.WriteTo(&mmio_);
}
void AmlPwm::EnableConst(uint32_t idx, bool on) {
fbl::AutoLock lock(&locks_[REG_MISC]);
auto misc_reg = MiscReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
misc_reg.set_constant_en_b(on);
} else {
misc_reg.set_constant_en_a(on);
}
misc_reg.WriteTo(&mmio_);
}
void AmlPwm::SetClock(uint32_t idx, uint8_t sel) {
fbl::AutoLock lock(&locks_[REG_MISC]);
auto misc_reg = MiscReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
misc_reg.set_clk_sel_b(sel);
} else {
misc_reg.set_clk_sel_a(sel);
}
misc_reg.WriteTo(&mmio_);
}
void AmlPwm::SetClockDivider(uint32_t idx, int divider) {
ZX_ASSERT(divider >= 1);
ZX_ASSERT(divider <= 128);
uint8_t divider_select = divider - 1;
fbl::AutoLock lock(&locks_[REG_MISC]);
auto misc_reg = MiscReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
misc_reg.set_clk_div_b(divider_select);
} else {
misc_reg.set_clk_div_a(divider_select);
}
misc_reg.WriteTo(&mmio_);
}
void AmlPwm::EnableBlink(uint32_t idx, bool on) {
fbl::AutoLock lock(&locks_[REG_BLINK]);
auto blink_reg = BlinkReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
blink_reg.set_enable_b(on);
} else {
blink_reg.set_enable_a(on);
}
blink_reg.WriteTo(&mmio_);
}
void AmlPwm::SetBlinkTimes(uint32_t idx, uint8_t times) {
fbl::AutoLock lock(&locks_[REG_BLINK]);
auto blink_reg = BlinkReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
blink_reg.set_times_b(times);
} else {
blink_reg.set_times_a(times);
}
blink_reg.WriteTo(&mmio_);
}
void AmlPwm::SetDSSetting(uint32_t idx, uint16_t val) {
fbl::AutoLock lock(&locks_[REG_DS]);
auto ds_reg = DeltaSigmaReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
ds_reg.set_b(val);
} else {
ds_reg.set_a(val);
}
ds_reg.WriteTo(&mmio_);
}
void AmlPwm::SetTimers(uint32_t idx, uint8_t timer1, uint8_t timer2) {
fbl::AutoLock lock(&locks_[REG_TIME]);
auto time_reg = TimeReg::Get().ReadFrom(&mmio_);
if (idx % 2) {
time_reg.set_b1(timer1).set_b2(timer2);
} else {
time_reg.set_a1(timer1).set_a2(timer2);
}
time_reg.WriteTo(&mmio_);
}
zx_status_t AmlPwmDevice::Create(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
std::unique_ptr<AmlPwmDevice> device(new (&ac) AmlPwmDevice(parent));
if (!ac.check()) {
zxlogf(ERROR, "%s: device object alloc failed", __func__);
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = ZX_OK;
if ((status = device->Init(parent)) != ZX_OK) {
zxlogf(ERROR, "%s: Init failed", __func__);
return status;
}
if (auto status = device->DdkAdd(ddk::DeviceAddArgs("aml-pwm-device")
.set_proto_id(ZX_PROTOCOL_PWM_IMPL)
.forward_metadata(parent, DEVICE_METADATA_PWM_IDS));
status != ZX_OK) {
zxlogf(ERROR, "%s: DdkAdd failed", __func__);
return status;
}
[[maybe_unused]] auto* unused = device.release();
return ZX_OK;
}
zx_status_t AmlPwmDevice::Init(zx_device_t* parent) {
zx_status_t status = ZX_OK;
auto pwm_ids = ddk::GetMetadataArray<pwm_id_t>(parent, DEVICE_METADATA_PWM_IDS);
if (!pwm_ids.is_ok()) {
return pwm_ids.error_value();
}
ddk::PDevFidl pdev(parent);
for (uint32_t i = 0;; i++) {
std::optional<fdf::MmioBuffer> mmio;
if ((status = pdev.MapMmio(i, &mmio)) != ZX_OK) {
break;
}
pwms_.push_back(std::make_unique<AmlPwm>(*std::move(mmio), pwm_ids.value()[2 * i],
pwm_ids.value()[2 * i + 1]));
pwms_.back()->Init();
}
return ZX_OK;
}
zx_status_t AmlPwmDevice::PwmImplGetConfig(uint32_t idx, pwm_config_t* out_config) {
if (idx >= pwms_.size() * 2 || out_config == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
return pwms_[idx / 2]->PwmImplGetConfig(idx % 2, out_config);
}
zx_status_t AmlPwmDevice::PwmImplSetConfig(uint32_t idx, const pwm_config_t* config) {
if (idx >= pwms_.size() * 2 || config == nullptr || config->mode_config_buffer == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
return pwms_[idx / 2]->PwmImplSetConfig(idx % 2, config);
}
zx_status_t AmlPwmDevice::PwmImplEnable(uint32_t idx) {
if (idx >= pwms_.size() * 2) {
return ZX_ERR_INVALID_ARGS;
}
return pwms_[idx / 2]->PwmImplEnable(idx % 2);
}
zx_status_t AmlPwmDevice::PwmImplDisable(uint32_t idx) {
if (idx >= pwms_.size() * 2) {
return ZX_ERR_INVALID_ARGS;
}
return pwms_[idx / 2]->PwmImplDisable(idx % 2);
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = AmlPwmDevice::Create;
return ops;
}();
} // namespace pwm
ZIRCON_DRIVER(pwm, pwm::driver_ops, "zircon", "0.1");