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