| /* |
| * 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 == 0U || 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 */ |