forked from zephyrproject-rtos/zephyr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pwm_mcux_tpm.c
220 lines (181 loc) · 5.69 KB
/
pwm_mcux_tpm.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
* Copyright 2019 Henrik Brix Andersen <[email protected]>
* Copyright 2020, 2024 NXP
*
* Heavily based on pwm_mcux_ftm.c, which is:
* Copyright (c) 2017, NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_kinetis_tpm
#include <zephyr/drivers/clock_control.h>
#include <errno.h>
#include <zephyr/drivers/pwm.h>
#include <soc.h>
#include <fsl_tpm.h>
#include <fsl_clock.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(pwm_mcux_tpm, CONFIG_PWM_LOG_LEVEL);
#if defined(TPM0)
#define MAX_CHANNELS ARRAY_SIZE(TPM0->CONTROLS)
#else
#define MAX_CHANNELS ARRAY_SIZE(TPM1->CONTROLS)
#endif
struct mcux_tpm_config {
TPM_Type *base;
const struct device *clock_dev;
clock_control_subsys_t clock_subsys;
tpm_clock_source_t tpm_clock_source;
tpm_clock_prescale_t prescale;
uint8_t channel_count;
tpm_pwm_mode_t mode;
const struct pinctrl_dev_config *pincfg;
};
struct mcux_tpm_data {
uint32_t clock_freq;
uint32_t period_cycles;
tpm_chnl_pwm_signal_param_t channel[MAX_CHANNELS];
};
static int mcux_tpm_set_cycles(const struct device *dev, uint32_t channel,
uint32_t period_cycles, uint32_t pulse_cycles,
pwm_flags_t flags)
{
const struct mcux_tpm_config *config = dev->config;
struct mcux_tpm_data *data = dev->data;
uint8_t duty_cycle;
if (period_cycles == 0U) {
LOG_ERR("Channel can not be set to inactive level");
return -ENOTSUP;
}
if (channel >= config->channel_count) {
LOG_ERR("Invalid channel");
return -ENOTSUP;
}
duty_cycle = pulse_cycles * 100U / period_cycles;
data->channel[channel].dutyCyclePercent = duty_cycle;
if ((flags & PWM_POLARITY_INVERTED) == 0) {
data->channel[channel].level = kTPM_HighTrue;
} else {
data->channel[channel].level = kTPM_LowTrue;
}
LOG_DBG("pulse_cycles=%d, period_cycles=%d, duty_cycle=%d, flags=%d",
pulse_cycles, period_cycles, duty_cycle, flags);
if (period_cycles != data->period_cycles) {
uint32_t pwm_freq;
status_t status;
if (data->period_cycles != 0) {
/* Only warn when not changing from zero */
LOG_WRN("Changing period cycles from %d to %d"
" affects all %d channels in %s",
data->period_cycles, period_cycles,
config->channel_count, dev->name);
}
data->period_cycles = period_cycles;
pwm_freq = (data->clock_freq >> config->prescale) /
period_cycles;
LOG_DBG("pwm_freq=%d, clock_freq=%d", pwm_freq,
data->clock_freq);
if (pwm_freq == 0U) {
LOG_ERR("Could not set up pwm_freq=%d", pwm_freq);
return -EINVAL;
}
TPM_StopTimer(config->base);
status = TPM_SetupPwm(config->base, data->channel,
config->channel_count, config->mode,
pwm_freq, data->clock_freq);
if (status != kStatus_Success) {
LOG_ERR("Could not set up pwm");
return -ENOTSUP;
}
TPM_StartTimer(config->base, config->tpm_clock_source);
} else {
TPM_UpdateChnlEdgeLevelSelect(config->base, channel,
data->channel[channel].level);
TPM_UpdatePwmDutycycle(config->base, channel, config->mode,
duty_cycle);
}
return 0;
}
static int mcux_tpm_get_cycles_per_sec(const struct device *dev,
uint32_t channel, uint64_t *cycles)
{
const struct mcux_tpm_config *config = dev->config;
struct mcux_tpm_data *data = dev->data;
*cycles = data->clock_freq >> config->prescale;
return 0;
}
static int mcux_tpm_init(const struct device *dev)
{
const struct mcux_tpm_config *config = dev->config;
struct mcux_tpm_data *data = dev->data;
tpm_chnl_pwm_signal_param_t *channel = data->channel;
tpm_config_t tpm_config;
int i;
int err;
if (config->channel_count > ARRAY_SIZE(data->channel)) {
LOG_ERR("Invalid channel count");
return -EINVAL;
}
if (!device_is_ready(config->clock_dev)) {
LOG_ERR("clock control device not ready");
return -ENODEV;
}
if (clock_control_on(config->clock_dev, config->clock_subsys)) {
LOG_ERR("Could not turn on clock");
return -EINVAL;
}
if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
&data->clock_freq)) {
LOG_ERR("Could not get clock frequency");
return -EINVAL;
}
for (i = 0; i < config->channel_count; i ) {
channel->chnlNumber = i;
#if !(defined(FSL_FEATURE_TPM_HAS_PAUSE_LEVEL_SELECT) && FSL_FEATURE_TPM_HAS_PAUSE_LEVEL_SELECT)
channel->level = kTPM_NoPwmSignal;
#else
channel->level = kTPM_HighTrue;
channel->pauseLevel = kTPM_ClearOnPause;
#endif
channel->dutyCyclePercent = 0;
#if defined(FSL_FEATURE_TPM_HAS_COMBINE) && FSL_FEATURE_TPM_HAS_COMBINE
channel->firstEdgeDelayPercent = 0;
#endif
channel ;
}
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
if (err) {
return err;
}
TPM_GetDefaultConfig(&tpm_config);
tpm_config.prescale = config->prescale;
TPM_Init(config->base, &tpm_config);
return 0;
}
static const struct pwm_driver_api mcux_tpm_driver_api = {
.set_cycles = mcux_tpm_set_cycles,
.get_cycles_per_sec = mcux_tpm_get_cycles_per_sec,
};
#define TPM_DEVICE(n) \
PINCTRL_DT_INST_DEFINE(n); \
static const struct mcux_tpm_config mcux_tpm_config_##n = { \
.base = (TPM_Type *) \
DT_INST_REG_ADDR(n), \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
.clock_subsys = (clock_control_subsys_t) \
DT_INST_CLOCKS_CELL(n, name), \
.tpm_clock_source = kTPM_SystemClock, \
.prescale = kTPM_Prescale_Divide_16, \
.channel_count = FSL_FEATURE_TPM_CHANNEL_COUNTn((TPM_Type *) \
DT_INST_REG_ADDR(n)), \
.mode = kTPM_EdgeAlignedPwm, \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
}; \
static struct mcux_tpm_data mcux_tpm_data_##n; \
DEVICE_DT_INST_DEFINE(n, &mcux_tpm_init, NULL, \
&mcux_tpm_data_##n, \
&mcux_tpm_config_##n, \
POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \
&mcux_tpm_driver_api);
DT_INST_FOREACH_STATUS_OKAY(TPM_DEVICE)