drivers: clock_control: Add PWM clock device
Adds a clock control device for a PWM node, allowing the PWM to be controlled using the clock control API. It is a similar idea to the device driver in linux: linux/Documentation/devicetree/bindings/clock/pwm-clock.yaml Signed-off-by: Andriy Gelman <andriy.gelman@gmail.com>
This commit is contained in:
parent
b485cd717b
commit
0d1fa268bb
13 changed files with 411 additions and 0 deletions
|
@ -28,6 +28,7 @@ zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NUMAKER_SCC clock_cont
|
||||||
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NXP_S32 clock_control_nxp_s32.c)
|
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_NXP_S32 clock_control_nxp_s32.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_RA clock_control_ra.c)
|
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_RA clock_control_ra.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_AMBIQ clock_control_ambiq.c)
|
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_AMBIQ clock_control_ambiq.c)
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_CLOCK_CONTROL_PWM clock_control_pwm.c)
|
||||||
|
|
||||||
|
|
||||||
if(CONFIG_CLOCK_CONTROL_STM32_CUBE)
|
if(CONFIG_CLOCK_CONTROL_STM32_CUBE)
|
||||||
|
|
|
@ -84,4 +84,6 @@ source "drivers/clock_control/Kconfig.ra"
|
||||||
|
|
||||||
source "drivers/clock_control/Kconfig.ambiq"
|
source "drivers/clock_control/Kconfig.ambiq"
|
||||||
|
|
||||||
|
source "drivers/clock_control/Kconfig.pwm"
|
||||||
|
|
||||||
endif # CLOCK_CONTROL
|
endif # CLOCK_CONTROL
|
||||||
|
|
18
drivers/clock_control/Kconfig.pwm
Normal file
18
drivers/clock_control/Kconfig.pwm
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Copyright (c) 2023 Andriy Gelman <andriy.gelman@gmail.com>
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
config CLOCK_CONTROL_PWM
|
||||||
|
bool "Generic PWM clock"
|
||||||
|
default y
|
||||||
|
depends on DT_HAS_PWM_CLOCK_ENABLED
|
||||||
|
select PWM
|
||||||
|
help
|
||||||
|
Enable generic PWM clock.
|
||||||
|
|
||||||
|
config CLOCK_CONTROL_PWM_INIT_PRIORITY
|
||||||
|
int "Initialization priority of the pwm clock device"
|
||||||
|
default 51
|
||||||
|
depends on CLOCK_CONTROL_PWM
|
||||||
|
help
|
||||||
|
Initialization priority of the PWM clock device. Must be
|
||||||
|
lower priority than PWM.
|
159
drivers/clock_control/clock_control_pwm.c
Normal file
159
drivers/clock_control/clock_control_pwm.c
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Andriy Gelman <andriy.gelman@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DT_DRV_COMPAT pwm_clock
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
#include <zephyr/drivers/clock_control.h>
|
||||||
|
#include <zephyr/drivers/pwm.h>
|
||||||
|
#include <zephyr/dt-bindings/pwm/pwm.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#define LOG_LEVEL CONFIG_CLOCK_CONTROL_LOG_LEVEL
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(clock_control_pwm);
|
||||||
|
|
||||||
|
BUILD_ASSERT(CONFIG_CLOCK_CONTROL_PWM_INIT_PRIORITY > CONFIG_PWM_INIT_PRIORITY,
|
||||||
|
"PWM must have a higher priority than PWM clock control");
|
||||||
|
|
||||||
|
#define NUM_PWM_CLOCKS 1
|
||||||
|
|
||||||
|
struct clock_control_pwm_config {
|
||||||
|
const struct pwm_dt_spec pwm_dt;
|
||||||
|
const uint16_t pwm_on_delay;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct clock_control_pwm_data {
|
||||||
|
uint32_t clock_frequency;
|
||||||
|
bool is_enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int clock_control_pwm_on(const struct device *dev, clock_control_subsys_t sys)
|
||||||
|
{
|
||||||
|
struct clock_control_pwm_data *data = dev->data;
|
||||||
|
const struct clock_control_pwm_config *config = dev->config;
|
||||||
|
const struct pwm_dt_spec *spec;
|
||||||
|
int id = (int)sys;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (id >= NUM_PWM_CLOCKS) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
spec = &config->pwm_dt;
|
||||||
|
if (data->clock_frequency == 0) {
|
||||||
|
ret = pwm_set_dt(spec, spec->period, spec->period / 2);
|
||||||
|
} else {
|
||||||
|
uint64_t cycles_per_sec;
|
||||||
|
uint32_t period_cycles;
|
||||||
|
|
||||||
|
ret = pwm_get_cycles_per_sec(spec->dev, spec->channel, &cycles_per_sec);
|
||||||
|
if (ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cycles_per_sec % data->clock_frequency > 0) {
|
||||||
|
LOG_WRN("Target clock frequency cannot be expressed in PWM clock ticks");
|
||||||
|
}
|
||||||
|
|
||||||
|
period_cycles = cycles_per_sec / data->clock_frequency;
|
||||||
|
ret = pwm_set_cycles(spec->dev, spec->channel, period_cycles, period_cycles / 2,
|
||||||
|
spec->flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_busy_wait(config->pwm_on_delay);
|
||||||
|
|
||||||
|
data->is_enabled = true;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clock_control_pwm_get_rate(const struct device *dev, clock_control_subsys_t sys,
|
||||||
|
uint32_t *rate)
|
||||||
|
{
|
||||||
|
struct clock_control_pwm_data *data = dev->data;
|
||||||
|
const struct clock_control_pwm_config *config = dev->config;
|
||||||
|
int id = (int)sys;
|
||||||
|
|
||||||
|
if (id >= NUM_PWM_CLOCKS) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->clock_frequency > 0) {
|
||||||
|
*rate = data->clock_frequency;
|
||||||
|
} else {
|
||||||
|
*rate = NSEC_PER_SEC / config->pwm_dt.period;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clock_control_pwm_set_rate(const struct device *dev, clock_control_subsys_rate_t sys,
|
||||||
|
clock_control_subsys_rate_t rate)
|
||||||
|
{
|
||||||
|
struct clock_control_pwm_data *data = dev->data;
|
||||||
|
uint32_t rate_hz = (uint32_t)rate;
|
||||||
|
int id = (int)sys;
|
||||||
|
|
||||||
|
if (id >= NUM_PWM_CLOCKS) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->clock_frequency == rate_hz && data->is_enabled) {
|
||||||
|
return -EALREADY;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->clock_frequency = rate_hz;
|
||||||
|
|
||||||
|
return clock_control_pwm_on(dev, sys);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clock_control_pwm_init(const struct device *dev)
|
||||||
|
{
|
||||||
|
const struct clock_control_pwm_config *config = dev->config;
|
||||||
|
|
||||||
|
if (!device_is_ready(config->pwm_dt.dev)) {
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct clock_control_driver_api clock_control_pwm_api = {
|
||||||
|
.on = clock_control_pwm_on,
|
||||||
|
.get_rate = clock_control_pwm_get_rate,
|
||||||
|
.set_rate = clock_control_pwm_set_rate,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PWM_CLOCK_INIT(i) \
|
||||||
|
\
|
||||||
|
BUILD_ASSERT(DT_INST_PROP_LEN(i, pwms) <= 1, \
|
||||||
|
"One PWM per clock control node is supported"); \
|
||||||
|
\
|
||||||
|
BUILD_ASSERT(DT_INST_PROP(i, pwm_on_delay) <= UINT16_MAX, \
|
||||||
|
"Maximum pwm-on-delay is 65535 usec"); \
|
||||||
|
\
|
||||||
|
static const struct clock_control_pwm_config clock_control_pwm_config_##i = { \
|
||||||
|
.pwm_dt = PWM_DT_SPEC_INST_GET(i), \
|
||||||
|
.pwm_on_delay = DT_INST_PROP(i, pwm_on_delay), \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
static struct clock_control_pwm_data clock_control_pwm_data_##i = { \
|
||||||
|
.clock_frequency = DT_INST_PROP_OR(i, clock_frequency, 0), \
|
||||||
|
}; \
|
||||||
|
\
|
||||||
|
DEVICE_DT_INST_DEFINE(i, clock_control_pwm_init, NULL, &clock_control_pwm_data_##i, \
|
||||||
|
&clock_control_pwm_config_##i, POST_KERNEL, \
|
||||||
|
CONFIG_CLOCK_CONTROL_PWM_INIT_PRIORITY, &clock_control_pwm_api);
|
||||||
|
|
||||||
|
DT_INST_FOREACH_STATUS_OKAY(PWM_CLOCK_INIT)
|
54
dts/bindings/clock/pwm-clock.yaml
Normal file
54
dts/bindings/clock/pwm-clock.yaml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# Copyright (c) 2023 Andriy Gelman
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
description: |
|
||||||
|
An external clock signal driven by a PWM pin.
|
||||||
|
|
||||||
|
The devicetree must define a clock node:
|
||||||
|
|
||||||
|
pwmclock: pwmclock {
|
||||||
|
status = "okay";
|
||||||
|
compatible = "pwm-clock";
|
||||||
|
#clock-cells = <1>;
|
||||||
|
pwms = <&pwm_ccu40 2 PWM_HZ(1000000) PWM_POLARITY_NORMAL>;
|
||||||
|
};
|
||||||
|
|
||||||
|
This will create a device node with a clock-controller
|
||||||
|
API. Internally the device node will use PWM API to start the
|
||||||
|
clock signals at 1MHz. Note that the PWM_HZ() macro converts the
|
||||||
|
frequency to time (nanoseconds units). This may result in rounding
|
||||||
|
errors if the clock frequency is not an integer number of nanoseconds.
|
||||||
|
The clock frequency can be explicitly set using the clock-frequency
|
||||||
|
property.
|
||||||
|
|
||||||
|
The PWM node may need to be properly configured to generate
|
||||||
|
the target period (i.e. using prescaler options). See the documention
|
||||||
|
for the target PWM driver.
|
||||||
|
|
||||||
|
compatible: "pwm-clock"
|
||||||
|
|
||||||
|
include: [clock-controller.yaml, base.yaml]
|
||||||
|
|
||||||
|
properties:
|
||||||
|
pwms:
|
||||||
|
type: phandle-array
|
||||||
|
required: true
|
||||||
|
|
||||||
|
clock-frequency:
|
||||||
|
type: int
|
||||||
|
description: |
|
||||||
|
Exact output frequency, in case the PWM period is not exact
|
||||||
|
but was rounded to nanoseconds. This property is optional.
|
||||||
|
|
||||||
|
pwm-on-delay:
|
||||||
|
type: int
|
||||||
|
default: 0
|
||||||
|
description:
|
||||||
|
Optional blocking delay in micro seconds to make sure that the PWM
|
||||||
|
clock has started after returning from clock_control_on().
|
||||||
|
|
||||||
|
"#clock-cells":
|
||||||
|
const: 1
|
||||||
|
|
||||||
|
clock-cells:
|
||||||
|
- id
|
7
tests/drivers/clock_control/pwm_clock/CMakeLists.txt
Normal file
7
tests/drivers/clock_control/pwm_clock/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
project(pwm_clock)
|
||||||
|
|
||||||
|
target_sources(app PRIVATE src/main.c)
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Andriy Gelman
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/ {
|
||||||
|
pwmclock: pwmclock {
|
||||||
|
status = "okay";
|
||||||
|
compatible = "pwm-clock";
|
||||||
|
#clock-cells = <1>;
|
||||||
|
clock-frequency = <1000000>;
|
||||||
|
pwms = <&pwm0 0 PWM_KHZ(1000) PWM_POLARITY_NORMAL>;
|
||||||
|
};
|
||||||
|
|
||||||
|
samplenode: samplenode {
|
||||||
|
status = "okay";
|
||||||
|
compatible = "test-clock-control-pwm-clock";
|
||||||
|
clocks = <&pwmclock 0>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&pwm0 {
|
||||||
|
prescaler = <1>;
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Andriy Gelman
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/ {
|
||||||
|
pwmclock: pwmclock {
|
||||||
|
status = "okay";
|
||||||
|
compatible = "pwm-clock";
|
||||||
|
#clock-cells = <1>;
|
||||||
|
clock-frequency = <1000000>;
|
||||||
|
pwms = <&pwm_ccu40 2 PWM_KHZ(1000) PWM_POLARITY_NORMAL>;
|
||||||
|
};
|
||||||
|
|
||||||
|
samplenode: samplenode {
|
||||||
|
status = "okay";
|
||||||
|
compatible = "test-clock-control-pwm-clock";
|
||||||
|
clocks = <&pwmclock 0>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&pwm_ccu40 {
|
||||||
|
status = "okay";
|
||||||
|
slice-prescaler = <0 0 0 0>;
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Andriy Gelman
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/ {
|
||||||
|
pwmclock: pwmclock {
|
||||||
|
status = "okay";
|
||||||
|
compatible = "pwm-clock";
|
||||||
|
#clock-cells = <1>;
|
||||||
|
clock-frequency = <1000000>;
|
||||||
|
pwms = <&pwm_ccu80 0 PWM_KHZ(1000) PWM_POLARITY_NORMAL>;
|
||||||
|
};
|
||||||
|
|
||||||
|
samplenode: samplenode {
|
||||||
|
status = "okay";
|
||||||
|
compatible = "test-clock-control-pwm-clock";
|
||||||
|
clocks = <&pwmclock 0>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&pwm_ccu80 {
|
||||||
|
status = "okay";
|
||||||
|
slice-prescaler = <0 0 0 0>;
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
description: Example binding for a node using a PWM clock
|
||||||
|
|
||||||
|
compatible: "test-clock-control-pwm-clock"
|
||||||
|
|
||||||
|
include: base.yaml
|
||||||
|
|
||||||
|
properties:
|
||||||
|
clocks:
|
||||||
|
required: true
|
||||||
|
description: Clock phandle array
|
2
tests/drivers/clock_control/pwm_clock/prj.conf
Normal file
2
tests/drivers/clock_control/pwm_clock/prj.conf
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
CONFIG_ZTEST=y
|
||||||
|
CONFIG_CLOCK_CONTROL=y
|
74
tests/drivers/clock_control/pwm_clock/src/main.c
Normal file
74
tests/drivers/clock_control/pwm_clock/src/main.c
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Andriy Gelman <andriy.gelman@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/sys/printk.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/devicetree.h>
|
||||||
|
#include <zephyr/drivers/clock_control.h>
|
||||||
|
#include <zephyr/ztest.h>
|
||||||
|
|
||||||
|
#define NODELABEL DT_NODELABEL(samplenode)
|
||||||
|
static const struct device *clk_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(NODELABEL));
|
||||||
|
|
||||||
|
static void *pwm_clock_setup(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
uint32_t clock_rate;
|
||||||
|
uint32_t clock_rate_dt = DT_PROP_BY_PHANDLE(NODELABEL, clocks, clock_frequency);
|
||||||
|
|
||||||
|
zassert_equal(device_is_ready(clk_dev), true, "%s: PWM clock device is not ready",
|
||||||
|
clk_dev->name);
|
||||||
|
|
||||||
|
ret = clock_control_get_rate(clk_dev, 0, &clock_rate);
|
||||||
|
zassert_equal(0, ret, "%s: Unexpected err (%d) from clock_control_get_rate",
|
||||||
|
clk_dev->name, ret);
|
||||||
|
|
||||||
|
zassert_equal(clock_rate_dt, clock_rate,
|
||||||
|
"%s: devicetree clock rate mismatch. Expected %dHz Fetched %dHz",
|
||||||
|
clk_dev->name, clock_rate_dt, clock_rate);
|
||||||
|
|
||||||
|
ret = clock_control_on(clk_dev, 0);
|
||||||
|
zassert_equal(0, ret, "%s: Unexpected err (%d) from clock_control_on", clk_dev->name, ret);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZTEST(pwm_clock, test_clock_control_get_rate)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
uint32_t clock_rate;
|
||||||
|
|
||||||
|
ret = clock_control_get_rate(clk_dev, 0, &clock_rate);
|
||||||
|
zassert_equal(0, ret, "%s: Unexpected err (%d) from clock_control_get_rate",
|
||||||
|
clk_dev->name, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZTEST(pwm_clock, test_clock_control_set_rate)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
uint32_t clock_rate, clock_rate_new;
|
||||||
|
|
||||||
|
ret = clock_control_get_rate(clk_dev, 0, &clock_rate);
|
||||||
|
zassert_equal(0, ret, "%s: Unexpected err (%d) from clock_control_get_rate",
|
||||||
|
clk_dev->name, ret);
|
||||||
|
|
||||||
|
clock_rate /= 2;
|
||||||
|
|
||||||
|
ret = clock_control_set_rate(clk_dev, 0, (clock_control_subsys_rate_t)clock_rate);
|
||||||
|
zassert_equal(0, ret, "%s: unexpected err (%d) from clock_control_set_rate",
|
||||||
|
clk_dev->name, ret);
|
||||||
|
|
||||||
|
ret = clock_control_get_rate(clk_dev, 0, &clock_rate_new);
|
||||||
|
zassert_equal(0, ret, "%s: Unexpected err (%d) from clock_control_get_rate",
|
||||||
|
clk_dev->name, ret);
|
||||||
|
|
||||||
|
zassert_equal(clock_rate, clock_rate_new,
|
||||||
|
"%s: Clock rate mismatch. Expected %dHz Fetched %dHz", clk_dev->name,
|
||||||
|
clock_rate, clock_rate_new);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZTEST_SUITE(pwm_clock, NULL, pwm_clock_setup, NULL, NULL, NULL);
|
7
tests/drivers/clock_control/pwm_clock/testcase.yaml
Normal file
7
tests/drivers/clock_control/pwm_clock/testcase.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
tests:
|
||||||
|
drivers.clock.pwm_clock:
|
||||||
|
filter: dt_compat_enabled("pwm-clock") and dt_compat_enabled("test-clock-control-pwm-clock")
|
||||||
|
tags:
|
||||||
|
- drivers
|
||||||
|
- clock
|
||||||
|
- pwm
|
Loading…
Add table
Add a link
Reference in a new issue