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:
Andriy Gelman 2023-06-11 17:07:42 -04:00 committed by Carles Cufí
commit 0d1fa268bb
13 changed files with 411 additions and 0 deletions

View file

@ -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)

View file

@ -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

View 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.

View 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)

View 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

View 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)

View file

@ -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>;
};

View file

@ -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>;
};

View file

@ -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>;
};

View file

@ -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

View file

@ -0,0 +1,2 @@
CONFIG_ZTEST=y
CONFIG_CLOCK_CONTROL=y

View 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);

View 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