275 lines
6.4 KiB
C
275 lines
6.4 KiB
C
/*
|
|
* Copyright (c) 2016 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <errno.h>
|
|
|
|
#include <pwm.h>
|
|
#include <device.h>
|
|
#include <kernel.h>
|
|
#include <init.h>
|
|
#include <power/power.h>
|
|
#include <misc/util.h>
|
|
|
|
#include "qm_pwm.h"
|
|
#include "clk.h"
|
|
|
|
#define HW_CLOCK_CYCLES_PER_USEC (CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC / \
|
|
USEC_PER_SEC)
|
|
|
|
/* pwm uses 32 bits counter to control low and high period */
|
|
#define MAX_LOW_PERIOD_IN_HW_CLOCK_CYCLES (((u64_t)1) << 32)
|
|
#define MAX_HIGH_PERIOD_IN_HW_CLOCK_CYCLES (((u64_t)1) << 32)
|
|
|
|
#define MAX_PERIOD_IN_HW_CLOCK_CYCLES (MAX_LOW_PERIOD_IN_HW_CLOCK_CYCLES + \
|
|
MAX_HIGH_PERIOD_IN_HW_CLOCK_CYCLES)
|
|
|
|
/* in micro seconds. */
|
|
#define MAX_PERIOD (MAX_PERIOD_IN_HW_CLOCK_CYCLES / HW_CLOCK_CYCLES_PER_USEC)
|
|
|
|
/**
|
|
* in micro seconds. To be able to get 1% granularity, MIN_PERIOD should
|
|
* have at least 100 HW clock cycles.
|
|
*/
|
|
#define MIN_PERIOD ((100 + (HW_CLOCK_CYCLES_PER_USEC - 1)) / \
|
|
HW_CLOCK_CYCLES_PER_USEC)
|
|
|
|
/* in micro seconds */
|
|
#define DEFAULT_PERIOD 2000
|
|
|
|
struct pwm_data {
|
|
#ifdef CONFIG_PWM_QMSI_API_REENTRANCY
|
|
struct k_sem sem;
|
|
#endif
|
|
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
|
|
u32_t device_power_state;
|
|
#endif
|
|
u32_t channel_period[CONFIG_PWM_QMSI_NUM_PORTS];
|
|
};
|
|
|
|
static struct pwm_data pwm_context;
|
|
|
|
#ifdef CONFIG_PWM_QMSI_API_REENTRANCY
|
|
#define RP_GET(dev) (&((struct pwm_data *)(dev->driver_data))->sem)
|
|
#else
|
|
#define RP_GET(dev) (NULL)
|
|
#endif
|
|
|
|
static int __set_one_port(struct device *dev, qm_pwm_t id, u32_t pwm,
|
|
u32_t on, u32_t off)
|
|
{
|
|
qm_pwm_config_t cfg;
|
|
int ret_val = 0;
|
|
|
|
if (IS_ENABLED(CONFIG_PWM_QMSI_API_REENTRANCY)) {
|
|
k_sem_take(RP_GET(dev), K_FOREVER);
|
|
}
|
|
|
|
/* Disable timer to prevent any output */
|
|
qm_pwm_stop(id, pwm);
|
|
|
|
if (on == 0U) {
|
|
/* stop PWM if so specified */
|
|
goto pwm_set_port_return;
|
|
}
|
|
|
|
/**
|
|
* off period must be more than zero. Otherwise, the PWM pin will be
|
|
* turned off. Let's use the minimum value which is 1 for this case.
|
|
*/
|
|
if (off == 0U) {
|
|
off = 1U;
|
|
}
|
|
|
|
/* PWM mode, user-defined count mode, timer disabled */
|
|
cfg.mode = QM_PWM_MODE_PWM;
|
|
|
|
/* No interrupts */
|
|
cfg.mask_interrupt = true;
|
|
cfg.callback = NULL;
|
|
cfg.callback_data = NULL;
|
|
|
|
/* Data for the timer to stay high and low */
|
|
cfg.hi_count = on;
|
|
cfg.lo_count = off;
|
|
|
|
if (qm_pwm_set_config(id, pwm, &cfg) != 0) {
|
|
ret_val = -EIO;
|
|
goto pwm_set_port_return;
|
|
}
|
|
|
|
/* Enable timer so it starts running and counting */
|
|
qm_pwm_start(id, pwm);
|
|
|
|
pwm_set_port_return:
|
|
if (IS_ENABLED(CONFIG_PWM_QMSI_API_REENTRANCY)) {
|
|
k_sem_give(RP_GET(dev));
|
|
}
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
/*
|
|
* Set the period and pulse width for a PWM pin.
|
|
*
|
|
* For example, with a nominal system clock of 32MHz, each count represents
|
|
* 31.25ns (e.g. period = 100 means the pulse is to repeat every 3125ns). The
|
|
* duration of one count depends on system clock. Refer to the hardware manual
|
|
* for more information.
|
|
*
|
|
* Parameters
|
|
* dev: Pointer to PWM device structure
|
|
* pwm: PWM port number to set
|
|
* period_cycles: Period (in timer count)
|
|
* pulse_cycles: Pulse width (in timer count).
|
|
*
|
|
* return 0, or negative errno code
|
|
*/
|
|
static int pwm_qmsi_pin_set(struct device *dev, u32_t pwm,
|
|
u32_t period_cycles, u32_t pulse_cycles)
|
|
{
|
|
u32_t high, low;
|
|
|
|
if (pwm >= CONFIG_PWM_QMSI_NUM_PORTS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (period_cycles == 0U || pulse_cycles > period_cycles) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
high = pulse_cycles;
|
|
low = period_cycles - pulse_cycles;
|
|
|
|
/*
|
|
* low must be more than zero. Otherwise, the PWM pin will be
|
|
* turned off. Let's make sure low is always more than zero.
|
|
*/
|
|
if (low == 0U) {
|
|
high--;
|
|
low = 1U;
|
|
}
|
|
|
|
return __set_one_port(dev, QM_PWM_0, pwm, high, low);
|
|
}
|
|
|
|
/*
|
|
* Get the clock rate (cycles per second) for a PWM pin.
|
|
*
|
|
* Parameters
|
|
* dev: Pointer to PWM device structure
|
|
* pwm: PWM port number
|
|
* cycles: Pointer to the memory to store clock rate (cycles per second)
|
|
*
|
|
* return 0, or negative errno code
|
|
*/
|
|
static int pwm_qmsi_get_cycles_per_sec(struct device *dev, u32_t pwm,
|
|
u64_t *cycles)
|
|
{
|
|
if (cycles == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
*cycles = (u64_t)clk_sys_get_ticks_per_us() * USEC_PER_SEC;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pwm_driver_api pwm_qmsi_drv_api_funcs = {
|
|
.pin_set = pwm_qmsi_pin_set,
|
|
.get_cycles_per_sec = pwm_qmsi_get_cycles_per_sec,
|
|
};
|
|
|
|
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
|
|
static void pwm_qmsi_set_power_state(struct device *dev, u32_t power_state)
|
|
{
|
|
struct pwm_data *context = dev->driver_data;
|
|
|
|
context->device_power_state = power_state;
|
|
}
|
|
#else
|
|
#define pwm_qmsi_set_power_state(...)
|
|
#endif
|
|
|
|
static int pwm_qmsi_init(struct device *dev)
|
|
{
|
|
struct pwm_data *context = dev->driver_data;
|
|
u32_t *channel_period = context->channel_period;
|
|
|
|
for (int i = 0; i < CONFIG_PWM_QMSI_NUM_PORTS; i++) {
|
|
channel_period[i] = DEFAULT_PERIOD *
|
|
HW_CLOCK_CYCLES_PER_USEC;
|
|
}
|
|
|
|
clk_periph_enable(CLK_PERIPH_PWM_REGISTER | CLK_PERIPH_CLK);
|
|
|
|
if (IS_ENABLED(CONFIG_PWM_QMSI_API_REENTRANCY)) {
|
|
k_sem_init(RP_GET(dev), 1, UINT_MAX);
|
|
}
|
|
|
|
pwm_qmsi_set_power_state(dev, DEVICE_PM_ACTIVE_STATE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
|
|
static qm_pwm_context_t pwm_ctx;
|
|
|
|
static u32_t pwm_qmsi_get_power_state(struct device *dev)
|
|
{
|
|
struct pwm_data *context = dev->driver_data;
|
|
|
|
return context->device_power_state;
|
|
}
|
|
|
|
static int pwm_qmsi_suspend(struct device *dev)
|
|
{
|
|
qm_pwm_save_context(QM_PWM_0, &pwm_ctx);
|
|
|
|
pwm_qmsi_set_power_state(dev, DEVICE_PM_SUSPEND_STATE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pwm_qmsi_resume_from_suspend(struct device *dev)
|
|
{
|
|
qm_pwm_restore_context(QM_PWM_0, &pwm_ctx);
|
|
|
|
pwm_qmsi_set_power_state(dev, DEVICE_PM_ACTIVE_STATE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Implements the driver control management functionality
|
|
* the *context may include IN data or/and OUT data
|
|
*/
|
|
static int pwm_qmsi_device_ctrl(struct device *dev, u32_t ctrl_command,
|
|
void *context, device_pm_cb cb, void *arg)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (ctrl_command == DEVICE_PM_SET_POWER_STATE) {
|
|
if (*((u32_t *)context) == DEVICE_PM_SUSPEND_STATE) {
|
|
ret = pwm_qmsi_suspend(dev);
|
|
} else if (*((u32_t *)context) == DEVICE_PM_ACTIVE_STATE) {
|
|
ret = pwm_qmsi_resume_from_suspend(dev);
|
|
}
|
|
} else if (ctrl_command == DEVICE_PM_GET_POWER_STATE) {
|
|
*((u32_t *)context) = pwm_qmsi_get_power_state(dev);
|
|
}
|
|
|
|
if (cb) {
|
|
cb(dev, ret, context, arg);
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
DEVICE_DEFINE(pwm_qmsi_0, CONFIG_PWM_QMSI_DEV_NAME, pwm_qmsi_init,
|
|
pwm_qmsi_device_ctrl, &pwm_context, NULL,
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
|
&pwm_qmsi_drv_api_funcs);
|