diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 1565e78ddff..7da589f95e9 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -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) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 4e7b334d977..6f39c8c6c8c 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -50,4 +50,6 @@ source "drivers/pwm/Kconfig.imx" source "drivers/pwm/Kconfig.esp32" +source "drivers/pwm/Kconfig.sam" + endif # PWM diff --git a/drivers/pwm/Kconfig.sam b/drivers/pwm/Kconfig.sam new file mode 100644 index 00000000000..6d34616c6c6 --- /dev/null +++ b/drivers/pwm/Kconfig.sam @@ -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. diff --git a/drivers/pwm/pwm_sam.c b/drivers/pwm/pwm_sam.c new file mode 100644 index 00000000000..80bb26e962d --- /dev/null +++ b/drivers/pwm/pwm_sam.c @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019 Aurelien Jarno + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_PWM_LOG_LEVEL +#include +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 */ diff --git a/dts/arm/atmel/same70.dtsi b/dts/arm/atmel/same70.dtsi index 77e480b74a4..97fb24bbeb8 100644 --- a/dts/arm/atmel/same70.dtsi +++ b/dts/arm/atmel/same70.dtsi @@ -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>; diff --git a/dts/bindings/pwm/atmel,sam-pwm.yaml b/dts/bindings/pwm/atmel,sam-pwm.yaml new file mode 100644 index 00000000000..b840cac7ffc --- /dev/null +++ b/dts/bindings/pwm/atmel,sam-pwm.yaml @@ -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 +... diff --git a/soc/arm/atmel_sam/same70/Kconfig.defconfig.series b/soc/arm/atmel_sam/same70/Kconfig.defconfig.series index 9822a0451b9..231bd7a7b5b 100644 --- a/soc/arm/atmel_sam/same70/Kconfig.defconfig.series +++ b/soc/arm/atmel_sam/same70/Kconfig.defconfig.series @@ -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