drivers: add Atmel SAM PWM driver

This patch adds basic support for the PWM devices available on the Atmel
SAM family. Beside enabling the driver, everything is selected through
the device tree, including enabling the PWM0 and PWM1 devices. Thus
CONFIG_PWM_0 and CONFIG_PWM_1 are ignored.

Signed-off-by: Aurelien Jarno <aurelien@aurel32.net>
This commit is contained in:
Aurelien Jarno 2019-01-26 13:32:55 +01:00 committed by Maureen Helm
commit 2168d80987
7 changed files with 217 additions and 0 deletions

View file

@ -9,5 +9,6 @@ zephyr_library_sources_ifdef(CONFIG_PWM_NRFX pwm_nrfx.c)
zephyr_library_sources_ifdef(CONFIG_PWM_MCUX_FTM pwm_mcux_ftm.c)
zephyr_library_sources_ifdef(CONFIG_PWM_IMX pwm_imx.c)
zephyr_library_sources_ifdef(CONFIG_PWM_LED_ESP32 pwm_led_esp32.c)
zephyr_library_sources_ifdef(CONFIG_PWM_SAM pwm_sam.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE pwm_handlers.c)

View file

@ -50,4 +50,6 @@ source "drivers/pwm/Kconfig.imx"
source "drivers/pwm/Kconfig.esp32"
source "drivers/pwm/Kconfig.sam"
endif # PWM

12
drivers/pwm/Kconfig.sam Normal file
View file

@ -0,0 +1,12 @@
# Kconfig.sam - Atmel SAM TRNG configuration
#
# Copyright (c) 2019 Aurelien Jarno
#
# SPDX-License-Identifier: Apache-2.0
menuconfig PWM_SAM
bool "Atmel SAM MCU Family PWM Driver"
depends on SOC_FAMILY_SAM
depends on PWM
help
Enable PWM driver for Atmel SAM MCUs.

119
drivers/pwm/pwm_sam.c Normal file
View file

@ -0,0 +1,119 @@
/*
* Copyright (c) 2019 Aurelien Jarno
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <errno.h>
#include <pwm.h>
#include <soc.h>
#define LOG_LEVEL CONFIG_PWM_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(pwm_sam);
struct sam_pwm_config {
Pwm *regs;
u32_t id;
u8_t prescaler;
u8_t divider;
};
#define DEV_CFG(dev) \
((const struct sam_pwm_config * const)(dev)->config->config_info)
static int sam_pwm_get_cycles_per_sec(struct device *dev, u32_t pwm,
u64_t *cycles)
{
u8_t prescaler = DEV_CFG(dev)->prescaler;
u8_t divider = DEV_CFG(dev)->divider;
*cycles = SOC_ATMEL_SAM_MCK_FREQ_HZ /
((1 << prescaler) * divider);
return 0;
}
static int sam_pwm_pin_set(struct device *dev, u32_t ch,
u32_t period_cycles, u32_t pulse_cycles)
{
Pwm *const pwm = DEV_CFG(dev)->regs;
if (ch >= PWMCHNUM_NUMBER) {
return -EINVAL;
}
if (period_cycles == 0 || pulse_cycles > period_cycles) {
return -EINVAL;
}
if (period_cycles > 0xffff) {
return -ENOTSUP;
}
/* Select clock A */
pwm->PWM_CH_NUM[ch].PWM_CMR = PWM_CMR_CPRE_CLKA_Val;
/* Update period and pulse using the update registers, so that the
* change is triggered at the next PWM period.
*/
pwm->PWM_CH_NUM[ch].PWM_CPRDUPD = period_cycles;
pwm->PWM_CH_NUM[ch].PWM_CDTYUPD = pulse_cycles;
/* Enable the output */
pwm->PWM_ENA = 1 << ch;
return 0;
}
static int sam_pwm_init(struct device *dev)
{
Pwm *const pwm = DEV_CFG(dev)->regs;
u32_t id = DEV_CFG(dev)->id;
u8_t prescaler = DEV_CFG(dev)->prescaler;
u8_t divider = DEV_CFG(dev)->divider;
/* FIXME: way to validate prescaler & divider */
/* Enable the PWM peripheral */
soc_pmc_peripheral_enable(id);
/* Configure the clock A that will be used by all 4 channels */
pwm->PWM_CLK = PWM_CLK_PREA(prescaler) | PWM_CLK_DIVA(divider);
return 0;
}
static const struct pwm_driver_api sam_pwm_driver_api = {
.pin_set = sam_pwm_pin_set,
.get_cycles_per_sec = sam_pwm_get_cycles_per_sec,
};
#ifdef DT_ATMEL_SAM_PWM_0
static const struct sam_pwm_config sam_pwm_config_0 = {
.regs = (Pwm *)DT_ATMEL_SAM_PWM_0_BASE_ADDRESS,
.id = DT_ATMEL_SAM_PWM_0_PERIPHERAL_ID,
.prescaler = DT_ATMEL_SAM_PWM_0_PRESCALER,
.divider = DT_ATMEL_SAM_PWM_0_DIVIDER,
};
DEVICE_AND_API_INIT(sam_pwm_0, DT_ATMEL_SAM_PWM_0_LABEL, &sam_pwm_init,
NULL, &sam_pwm_config_0,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&sam_pwm_driver_api);
#endif /* DT_ATMEL_SAM_PWM_0 */
#ifdef DT_ATMEL_SAM_PWM_1
static const struct sam_pwm_config sam_pwm_config_1 = {
.regs = (Pwm *)DT_ATMEL_SAM_PWM_1_BASE_ADDRESS,
.id = DT_ATMEL_SAM_PWM_1_PERIPHERAL_ID,
.prescaler = DT_ATMEL_SAM_PWM_1_PRESCALER,
.divider = DT_ATMEL_SAM_PWM_1_DIVIDER,
};
DEVICE_AND_API_INIT(sam_pwm_1, DT_ATMEL_SAM_PWM_1_LABEL, &sam_pwm_init,
NULL, &sam_pwm_config_1,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&sam_pwm_driver_api);
#endif /* DT_ATMEL_SAM_PWM_1 */

View file

@ -254,6 +254,30 @@
#gpio-cells = <2>;
};
pwm0: pwm0@40020000 {
compatible = "atmel,sam-pwm";
reg = <0x40020000 0x4000>;
interrupts = <31 0>;
peripheral-id = <31>;
status = "disabled";
label = "PWM_0";
prescaler = <10>;
divider = <1>;
#pwm-cells = <2>;
};
pwm1: pwm1@4005c000 {
compatible = "atmel,sam-pwm";
reg = <0x4005c000 0x4000>;
interrupts = <60 0>;
peripheral-id = <60>;
status = "disabled";
label = "PWM_1";
prescaler = <10>;
divider = <1>;
#pwm-cells = <2>;
};
usbhs: usbd@40038000 {
compatible = "atmel,sam-usbhs";
#address-cells = <1>;

View file

@ -0,0 +1,54 @@
#
# Copyright (c) 2019, Aurelien Jarno
#
# SPDX-License-Identifier: Apache-2.0
#
---
title: Atmel SAM PWM
version: 0.1
description: >
This binding gives a base representation of the Atmel SAM PWM
inherits:
!include pwm.yaml
properties:
compatible:
constraint: "atmel,sam-pwm"
reg:
type: array
description: mmio register space
generation: define
category: required
interrupts:
type: array
category: required
description: required interrupts
generation: define
peripheral-id:
type: int
description: peripheral ID
generation: define
category: required
prescaler:
type: int
category: required
description: Clock prescaler at the input of the PWM (0 to 10)
generation: define
divider:
type: int
category: required
description: Clock divider at the input of the PWM (1 to 255)
generation: define
"#cells":
- channel
# period in terms of nanoseconds
- period
...

View file

@ -83,4 +83,9 @@ config SOC_FLASH_SAM
default y
endif # FLASH
if PWM
config PWM_SAM
default y
endif # PWM
endif # SOC_SERIES_SAME70