Files
OpenCellular/chip/npcx/pwm.c
Mulin Chao e43ba03ebf npcx: Move pwm open-drain functionality from gpio to pwm driver.
Setting PWM IO type in gpio driver seems not a proper way. This
CL moves this functionality to pwm driver and introduces a new
flag PWM_CONFIG_OPEN_DRAIN to achieve it when user declared it
in board driver.

BRANCH=none
BUG=none
TEST=test pwm functionality on npcx_evb.

Change-Id: I90c60445d1fb10902244ddf0f635d8304e72f4ab
Signed-off-by: Mulin Chao <mlchao@nuvoton.com>
Reviewed-on: https://chromium-review.googlesource.com/458043
Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
2017-03-24 06:49:55 -07:00

259 lines
5.7 KiB
C

/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* PWM control module for NPCX.
*
* On this chip, the PWM logic is implemented by the hardware FAN modules.
*/
#include "assert.h"
#include "clock.h"
#include "clock_chip.h"
#include "console.h"
#include "ec_commands.h"
#include "fan.h"
#include "gpio.h"
#include "hooks.h"
#include "pwm.h"
#include "pwm_chip.h"
#include "registers.h"
#include "util.h"
#if !(DEBUG_PWM)
#define CPRINTS(...)
#else
#define CPRINTS(format, args...) cprints(CC_PWM, format, ## args)
#endif
/* pwm resolution for each channel */
static uint32_t pwm_res[PWM_CH_COUNT];
/* PWM clock source */
enum npcx_pwm_source_clock {
NPCX_PWM_CLOCK_APB2_LFCLK = 0,
NPCX_PWM_CLOCK_FX = 1,
NPCX_PWM_CLOCK_FR = 2,
NPCX_PWM_CLOCK_RESERVED = 3,
NPCX_PWM_CLOCK_UNDEF = 0xFF
};
/* PWM heartbeat mode */
enum npcx_pwm_heartbeat_mode {
NPCX_PWM_HBM_NORMAL = 0,
NPCX_PWM_HBM_25 = 1,
NPCX_PWM_HBM_50 = 2,
NPCX_PWM_HBM_100 = 3,
NPCX_PWM_HBM_UNDEF = 0xFF
};
/**
* Set PWM operation clock.
*
* @param ch operation channel
* @param freq desired PWM frequency
* @notes changed when initialization
*/
void pwm_set_freq(enum pwm_channel ch, uint32_t freq)
{
int mdl = pwm_channels[ch].channel;
uint32_t clock;
uint32_t pre;
assert(freq != 0);
/* Disable PWM for module configuration */
pwm_enable(ch, 0);
/*
* Get PWM clock frequency. Use internal 32K as PWM clock source if
* the PWM must be active during low-power idle.
*/
if (pwm_channels[ch].flags & PWM_CONFIG_DSLEEP)
clock = INT_32K_CLOCK;
else
clock = clock_get_apb2_freq();
/* Calculate prescaler */
pre = DIV_ROUND_UP(clock, (0xffff * freq));
/* Calculate maximum resolution for the given freq. and prescaler */
pwm_res[ch] = (clock / pre) / freq;
/* Set PWM prescaler. */
NPCX_PRSC(mdl) = pre - 1;
/* Set PWM cycle time */
NPCX_CTR(mdl) = pwm_res[ch];
/* Set the duty cycle to 100% since DCR == CTR */
NPCX_DCR(mdl) = pwm_res[ch];
}
/**
* Set PWM enabled.
*
* @param ch operation channel
* @param enabled enabled flag
* @return none
*/
void pwm_enable(enum pwm_channel ch, int enabled)
{
int mdl = pwm_channels[ch].channel;
/* Start or close PWM module */
UPDATE_BIT(NPCX_PWMCTL(mdl), NPCX_PWMCTL_PWR, enabled);
}
/**
* Check PWM enabled.
*
* @param ch operation channel
* @return enabled or not
*/
int pwm_get_enabled(enum pwm_channel ch)
{
int mdl = pwm_channels[ch].channel;
return IS_BIT_SET(NPCX_PWMCTL(mdl), NPCX_PWMCTL_PWR);
}
/**
* Set PWM duty cycle.
*
* @param ch operation channel
* @param percent duty cycle percent
* @return none
*/
void pwm_set_duty(enum pwm_channel ch, int percent)
{
/* Convert 16 bit duty to percent on [0, 100] */
pwm_set_raw_duty(ch, (percent * EC_PWM_MAX_DUTY) / 100);
}
/**
* Set PWM duty cycle.
*
* @param ch operation channel
* @param duty cycle duty
* @return none
*/
void pwm_set_raw_duty(enum pwm_channel ch, uint16_t duty)
{
int mdl = pwm_channels[ch].channel;
uint32_t sd;
CPRINTS("pwm%d, set duty=%d", mdl, duty);
/* Assume the fan control is active high and invert it ourselves */
UPDATE_BIT(NPCX_PWMCTL(mdl), NPCX_PWMCTL_INVP,
(pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW));
CPRINTS("initial freq=0x%x", pwm_channels[ch].freq);
CPRINTS("duty_cycle_cnt=%d", duty);
/* duty ranges from 0 - 0xffff, so scale down to 0 - pwm_res[ch] */
sd = DIV_ROUND_NEAREST(duty * pwm_res[ch], EC_PWM_MAX_DUTY);
/* Set the duty cycle */
NPCX_DCR(mdl) = (uint16_t)sd;
pwm_enable(ch, !!duty);
}
/**
* Get PWM duty cycle.
*
* @param ch operation channel
* @return duty cycle percent
*/
int pwm_get_duty(enum pwm_channel ch)
{
/* duty ranges from 0 - 0xffff, so scale to 0 - 100 */
return DIV_ROUND_NEAREST(pwm_get_raw_duty(ch) * 100, EC_PWM_MAX_DUTY);
}
/**
* Get PWM duty cycle.
*
* @param ch operation channel
* @return duty cycle
*/
uint16_t pwm_get_raw_duty(enum pwm_channel ch)
{
int mdl = pwm_channels[ch].channel;
/* Return duty */
if (!pwm_get_enabled(ch))
return 0;
else
/*
* NPCX_DCR ranges from 0 - pwm_res[ch],
* so scale to 0 - 0xffff
*/
return DIV_ROUND_NEAREST(NPCX_DCR(mdl) * EC_PWM_MAX_DUTY,
pwm_res[ch]);
}
/**
* PWM configuration.
*
* @param ch operation channel
* @return none
*/
void pwm_config(enum pwm_channel ch)
{
int mdl = pwm_channels[ch].channel;
/* Disable PWM for module configuration */
pwm_enable(ch, 0);
/* Set PWM heartbeat mode is no heartbeat */
SET_FIELD(NPCX_PWMCTL(mdl), NPCX_PWMCTL_HB_DC_CTL_FIELD,
NPCX_PWM_HBM_NORMAL);
/* Select default CLK or LFCLK clock input to PWM module */
SET_FIELD(NPCX_PWMCTLEX(mdl), NPCX_PWMCTLEX_FCK_SEL_FIELD,
NPCX_PWM_CLOCK_APB2_LFCLK);
/* Set PWM polarity normal first */
CLEAR_BIT(NPCX_PWMCTL(mdl), NPCX_PWMCTL_INVP);
/* Select PWM clock source */
UPDATE_BIT(NPCX_PWMCTL(mdl), NPCX_PWMCTL_CKSEL,
(pwm_channels[ch].flags & PWM_CONFIG_DSLEEP));
/* Select PWM IO type */
UPDATE_BIT(NPCX_PWMCTLEX(mdl), NPCX_PWMCTLEX_OD_OUT,
(pwm_channels[ch].flags & PWM_CONFIG_OPEN_DRAIN));
/* Set PWM operation frequency */
pwm_set_freq(ch, pwm_channels[ch].freq);
}
/**
* PWM initial.
*
* @param none
* @return none
*/
static void pwm_init(void)
{
int i;
uint8_t pd_mask = 0;
/* Take enabled PWMs out of power-down state */
for (i = 0; i < PWM_CH_COUNT; i++) {
pd_mask |= (1 << pwm_channels[i].channel);
pwm_res[i] = 0;
}
clock_enable_peripheral(CGC_OFFSET_PWM, pd_mask, CGC_MODE_ALL);
for (i = 0; i < PWM_CH_COUNT; i++)
pwm_config(i);
}
/* The chip-specific fan module initializes before this. */
DECLARE_HOOK(HOOK_INIT, pwm_init, HOOK_PRIO_INIT_PWM);