blob: 9d16687ddf0c44581d21b5543be7880785724544 [file] [log] [blame]
/* 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 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
*/
static void pwm_set_freq(enum pwm_channel ch, uint32_t freq)
{
int mdl = pwm_channels[ch].channel;
uint32_t prescaler_divider;
uint32_t clock;
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();
/*
* Based on freq = clock / ((ctr + 1) * (prsc + 1))
* where: prsc = prescaler_divider
* ctr = MAX_DUTY_CYCLE
*/
prescaler_divider = (clock / ((EC_PWM_MAX_DUTY + 1) * freq)) - 1;
/* Configure computed prescaler and resolution */
NPCX_PRSC(mdl) = (uint16_t)prescaler_divider;
/* Set PWM cycle time */
NPCX_CTR(mdl) = EC_PWM_MAX_DUTY - 1;
/* Set the duty cycle to 0% since DCR > CTR */
NPCX_DCR(mdl) = EC_PWM_MAX_DUTY;
}
/**
* 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;
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("freq=0x%x", pwm_channels[ch].freq);
CPRINTS("duty_cycle_cnt=%d", duty);
/* Set the duty cycle */
NPCX_DCR(mdl) = (uint16_t)duty;
pwm_enable(ch, !!duty);
}
/**
* Get PWM duty cycle.
*
* @param ch operation channel
* @return duty cycle percent
*/
int pwm_get_duty(enum pwm_channel ch)
{
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
return NPCX_DCR(mdl);
}
/**
* 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));
/* 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);
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);