| /* |
| * Copyright (c) 2015 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file Driver for DesignWare PWM driver. |
| * |
| * The timer IP block can act as both timer and PWM. Under PWM mode, each port |
| * has two registers to specify how long to stay low, and how long to stay high. |
| * Care must be taken so that PWM and timer functions are not both enabled |
| * on one port. |
| * |
| * The set of registers for each timer repeats every 0x14. |
| * However, the load count 2 starts at 0xB0, and repeats every 0x04. |
| * Accessing load count 2 registers, thus, requires some special treatment. |
| */ |
| |
| #include <errno.h> |
| |
| #include <kernel.h> |
| #include <board.h> |
| #include <pwm.h> |
| |
| /* Register for component version */ |
| #define REG_COMP_VER 0xAC |
| |
| /* Timer Load Count register, for pin to stay low. */ |
| #define REG_TMR_LOAD_CNT 0x00 |
| |
| /* Control for timer */ |
| #define REG_TMR_CTRL 0x08 |
| |
| /* Offset from Timer 1 Load Count address |
| * for other timers. (e.g. Timer 2 address +0x14, |
| * timer 3 address + 0x28, etc.) |
| * |
| * This also applies to other registers for |
| * different timers (except load count 2). |
| */ |
| #define REG_OFFSET 0x14 |
| |
| /* Timer Load Count 2 register, for pin to stay high. */ |
| #define REG_TMR_LOAD_CNT2 0xB0 |
| |
| /* Offset from Timer 1 Load Count 2 address |
| * for other timers. (e.g. Timer 2 address +0x04, |
| * timer 3 address + 0x08, etc.) |
| */ |
| #define REG_OFFSET_LOAD_CNT2 0x04 |
| |
| /* Default for control register: |
| * PWM mode, interrupt masked, user-defined count mode, but disabled |
| */ |
| #define TIMER_INIT_CTRL 0x0E |
| |
| struct pwm_dw_config { |
| /** Base address of registers */ |
| u32_t addr; |
| |
| /** Number of ports */ |
| u32_t num_ports; |
| }; |
| |
| /** |
| * Find the base address for each timer |
| * |
| * @param dev Device struct |
| * @param timer Which timer |
| * |
| * @return The base address of that particular timer |
| */ |
| static inline int pwm_dw_timer_base_addr(struct device *dev, u32_t timer) |
| { |
| const struct pwm_dw_config * const cfg = |
| (struct pwm_dw_config *)dev->config->config_info; |
| |
| return (cfg->addr + (timer * REG_OFFSET)); |
| } |
| |
| /** |
| * Find the load count 2 address for each timer |
| * |
| * @param dev Device struct |
| * @param timer Which timer |
| * |
| * @return The load count 2 address of that particular timer |
| */ |
| static inline int pwm_dw_timer_ldcnt2_addr(struct device *dev, u32_t timer) |
| { |
| const struct pwm_dw_config * const cfg = |
| (struct pwm_dw_config *)dev->config->config_info; |
| |
| return (cfg->addr + REG_TMR_LOAD_CNT2 + (timer * REG_OFFSET_LOAD_CNT2)); |
| } |
| |
| |
| static int __set_one_port(struct device *dev, u32_t pwm, |
| u32_t on, u32_t off) |
| { |
| u32_t reg_addr; |
| |
| reg_addr = pwm_dw_timer_base_addr(dev, pwm); |
| |
| /* Disable timer to prevent any output */ |
| sys_write32(TIMER_INIT_CTRL, (reg_addr + REG_TMR_CTRL)); |
| |
| if ((off == 0) || (on == 0)) { |
| /* stop PWM if so specified */ |
| return 0; |
| } |
| |
| /* write timer for pin to stay low */ |
| sys_write32(off, (reg_addr + REG_TMR_LOAD_CNT)); |
| |
| /* write timer for pin to stay high */ |
| sys_write32(on, pwm_dw_timer_ldcnt2_addr(dev, pwm)); |
| |
| /* Enable timer so it starts running and counting */ |
| sys_write32((TIMER_INIT_CTRL | 0x01), (reg_addr + REG_TMR_CTRL)); |
| |
| return 0; |
| } |
| |
| /** |
| * Set the period and the pulse of PWM. |
| * |
| * |
| * Assumes a nominal system clock of 32MHz, each count of on/off represents |
| * 31.25ns (e.g. on == 2 means the pin stays high for 62.5ns). |
| * The duration of 1 count depends on system clock. Refer to the hardware |
| * manual for more information. |
| * |
| * @param dev Device struct |
| * @param pwm Which PWM pin to set |
| * @param period_cycles Period in clock cycles of the pwm. |
| * @param pulse_cycles PWM width in clock cycles |
| * |
| * @return 0 |
| */ |
| static int pwm_dw_pin_set_cycles(struct device *dev, |
| u32_t pwm, u32_t period_cycles, u32_t pulse_cycles) |
| { |
| const struct pwm_dw_config * const cfg = |
| (struct pwm_dw_config *)dev->config->config_info; |
| int i; |
| u32_t on, off; |
| |
| /* make sure the PWM port exists */ |
| if (pwm >= cfg->num_ports) { |
| return -EIO; |
| } |
| |
| if (period_cycles == 0 || pulse_cycles > period_cycles) { |
| return -EINVAL; |
| } |
| on = pulse_cycles; |
| off = period_cycles - pulse_cycles; |
| |
| if (off == 0) { |
| on--; |
| off++; |
| } |
| |
| return __set_one_port(dev, pwm, on, off); |
| |
| } |
| |
| static struct pwm_driver_api pwm_dw_drv_api_funcs = { |
| .pin_set = pwm_dw_pin_set_cycles, |
| }; |
| |
| /** |
| * @brief Initialization function of PCA9685 |
| * |
| * @param dev Device struct |
| * @return 0 if successful, failed otherwise. |
| */ |
| int pwm_dw_init(struct device *dev) |
| { |
| return 0; |
| } |
| |
| /* Initialization for PWM_DW */ |
| #if defined(CONFIG_PWM_DW) |
| #include <device.h> |
| #include <init.h> |
| |
| static struct pwm_dw_config pwm_dw_cfg = { |
| .addr = PWM_DW_BASE_ADDR, |
| .num_ports = PWM_DW_NUM_PORTS, |
| }; |
| |
| DEVICE_AND_API_INIT(pwm_dw_0, CONFIG_PWM_DW_0_DRV_NAME, pwm_dw_init, |
| NULL, &pwm_dw_cfg, |
| POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, |
| &pwm_dw_drv_api_funcs); |
| |
| #endif /* CONFIG_PWM_DW */ |