1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "aml-pwm.h"
6 #include "aml-pwm-regs.h"
7 #include <ddk/debug.h>
8 #include <fbl/auto_call.h>
9 #include <fbl/auto_lock.h>
10 #include <hw/reg.h>
11 #include <threads.h>
12 #include <unistd.h>
13
14 namespace thermal {
15
16 namespace {
17
18 // MMIO index.
19 constexpr uint32_t kPwmMmio = 3;
20
21 // Input clock frequency
22 constexpr uint32_t kXtalFreq = 24000000;
23
24 } // namespace
25
Init(zx_device_t * parent)26 zx_status_t AmlPwm::Init(zx_device_t* parent) {
27 zx_status_t status = device_get_protocol(parent,
28 ZX_PROTOCOL_PDEV,
29 &pdev_);
30 if (status != ZX_OK) {
31 return status;
32 }
33
34 // Map amlogic pwm registers
35 mmio_buffer_t mmio;
36 status = pdev_map_mmio_buffer2(&pdev_, kPwmMmio, ZX_CACHE_POLICY_UNCACHED_DEVICE,
37 &mmio);
38 if (status != ZX_OK) {
39 zxlogf(ERROR, "aml-pwm: could not map periph mmio: %d\n", status);
40 return status;
41 }
42
43 pwm_mmio_ = ddk::MmioBuffer(mmio);
44
45 switch (hwpwm_) {
46 case 0:
47 pwm_duty_cycle_offset_ = S905D2_AO_PWM_PWM_A;
48 enable_bit_ = A_ENABLE;
49 clk_enable_bit_ = CLK_A_ENABLE;
50 break;
51 case 1:
52 pwm_duty_cycle_offset_ = S905D2_AO_PWM_PWM_B;
53 enable_bit_ = B_ENABLE;
54 clk_enable_bit_ = CLK_B_ENABLE;
55 break;
56 default:
57 return ZX_ERR_INVALID_ARGS;
58 }
59 return ZX_OK;
60 }
61
Configure(uint32_t duty_cycle)62 zx_status_t AmlPwm::Configure(uint32_t duty_cycle) {
63 uint16_t low_count;
64 uint16_t high_count;
65 const uint64_t fin_ns = NSEC_PER_SEC / kXtalFreq;
66
67 if (duty_cycle > 100) {
68 return ZX_ERR_INVALID_ARGS;
69 }
70
71 // If the current duty cycle is the same as requested, no need to do anything.
72 if (duty_cycle == duty_cycle_) {
73 return ZX_OK;
74 }
75
76 // Calculate the high and low count first based on the duty cycle requested.
77 const uint32_t duty = (duty_cycle * period_) / 100;
78 const uint16_t count = static_cast<uint16_t>(period_ / fin_ns);
79
80 if (duty == period_) {
81 high_count = count;
82 low_count = 0;
83 } else if (duty == 0) {
84 high_count = 0;
85 low_count = count;
86 } else {
87 const uint16_t duty_count = static_cast<uint16_t>(duty / fin_ns);
88 high_count = duty_count;
89 low_count = static_cast<uint16_t>(count - duty_count);
90 }
91
92 fbl::AutoLock lock(&pwm_lock_);
93
94 const uint32_t value = (high_count << PWM_HIGH_SHIFT) | low_count;
95 pwm_mmio_->Write32(value, pwm_duty_cycle_offset_);
96 pwm_mmio_->SetBits32(enable_bit_ | clk_enable_bit_, S905D2_AO_PWM_MISC_REG_AB);
97
98 // Update the new duty_cycle information
99 duty_cycle_ = duty_cycle;
100 return ZX_OK;
101 }
102
103 } // namespace thermal
104