2015-12-23 04:34:27 +08:00
|
|
|
/*
|
|
|
|
* 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,
|
|
|
|
};
|
|
|
|
|
2016-01-29 03:48:47 +08:00
|
|
|
DEVICE_INIT(pwm_dw_0, CONFIG_PWM_DW_DEV_NAME, pwm_dw_init,
|
|
|
|
NULL, &pwm_dw_cfg,
|
|
|
|
SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|
2015-12-23 04:34:27 +08:00
|
|
|
|
|
|
|
#endif /* CONFIG_PWM_DW */
|