| /* |
| * Copyright (c) 2015 Intel Corporation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @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 <nanokernel.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 */ |
| uint32_t addr; |
| |
| /** Number of ports */ |
| uint32_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, uint32_t timer) |
| { |
| 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, uint32_t timer) |
| { |
| 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 pwm_dw_configure(struct device *dev, int access_op, |
| uint32_t pwm, int flags) |
| { |
| ARG_UNUSED(dev); |
| ARG_UNUSED(access_op); |
| ARG_UNUSED(pwm); |
| ARG_UNUSED(flags); |
| |
| return DEV_OK; |
| } |
| |
| static int __set_one_port(struct device *dev, uint32_t pwm, |
| uint32_t on, uint32_t off) |
| { |
| uint32_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 DEV_OK; |
| } |
| |
| /* 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 DEV_OK; |
| } |
| |
| /** |
| * Set the duration for on/off timer of PWM. |
| * |
| * This sets the duration for the pin to low or high. |
| * |
| * 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 access_op whether to set one pin or all |
| * @param pwm Which PWM port to set, 0 if addressing all |
| * @param on Duration for pin to stay high (must be >= 2) |
| * @param off Duration for pin to stay low (must be >= 2) |
| * |
| * @return DEV_OK |
| */ |
| static int pwm_dw_set_values(struct device *dev, int access_op, |
| uint32_t pwm, uint32_t on, uint32_t off) |
| { |
| struct pwm_dw_config * const cfg = |
| (struct pwm_dw_config *)dev->config->config_info; |
| int i; |
| |
| switch (access_op) { |
| case PWM_ACCESS_BY_PIN: |
| /* make sure the PWM port exists */ |
| if (pwm >= cfg->num_ports) { |
| return DEV_FAIL; |
| } |
| |
| return __set_one_port(dev, pwm, on, off); |
| case PWM_ACCESS_ALL: |
| for (i = 0; i < cfg->num_ports; i++) { |
| __set_one_port(dev, i, on, off); |
| } |
| |
| return DEV_OK; |
| } |
| |
| return DEV_INVALID_OP; |
| } |
| |
| static int pwm_dw_set_duty_cycle(struct device *dev, int access_op, |
| uint32_t pwm, uint8_t duty) |
| { |
| /* The IP block does not natively support duty cycle settings. |
| * So need to use set_values(). |
| **/ |
| |
| ARG_UNUSED(dev); |
| ARG_UNUSED(access_op); |
| ARG_UNUSED(pwm); |
| ARG_UNUSED(duty); |
| |
| return DEV_INVALID_OP; |
| } |
| |
| static int pwm_dw_suspend(struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| return DEV_INVALID_OP; |
| } |
| |
| static int pwm_dw_resume(struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| return DEV_INVALID_OP; |
| } |
| |
| static struct pwm_driver_api pwm_dw_drv_api_funcs = { |
| .config = pwm_dw_configure, |
| .set_values = pwm_dw_set_values, |
| .set_duty_cycle = pwm_dw_set_duty_cycle, |
| .suspend = pwm_dw_suspend, |
| .resume = pwm_dw_resume, |
| }; |
| |
| /** |
| * @brief Initialization function of PCA9685 |
| * |
| * @param dev Device struct |
| * @return DEV_OK if successful, failed otherwise. |
| */ |
| int pwm_dw_init(struct device *dev) |
| { |
| dev->driver_api = &pwm_dw_drv_api_funcs; |
| |
| return DEV_OK; |
| } |
| |
| /* Initialization for PWM_DW */ |
| #if defined(CONFIG_PWM_DW) |
| #include <device.h> |
| #include <init.h> |
| |
| static struct pwm_dw_config pwm_dw_cfg = { |
| .addr = CONFIG_PWM_DW_BASE_ADDR, |
| .num_ports = CONFIG_PWM_DW_NUM_PORTS, |
| }; |
| |
| DEVICE_INIT(pwm_dw_0, CONFIG_PWM_DW_DEV_NAME, pwm_dw_init, |
| NULL, &pwm_dw_cfg, |
| SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE); |
| |
| #endif /* CONFIG_PWM_DW */ |