/* Copyright 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 "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 */ static 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. And * prevent it exceed the resolution of CTR/DCR registers. */ pwm_res[ch] = MIN((clock / pre) / freq, NPCX_PWM_MAX_RAW_DUTY); /* 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 */ 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 */ void pwm_set_duty(enum pwm_channel ch, int percent) { /* Convert percent on [0, 100] to 16 bit duty */ pwm_set_raw_duty(ch, (percent * EC_PWM_MAX_DUTY) / 100); } /** * Set PWM duty cycle. * * @param ch operation channel * @param duty cycle duty */ 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. If it is zero, set DCR > CTR */ NPCX_DCR(mdl) = sd ? sd : NPCX_PWM_MAX_RAW_DUTY + 1; } /** * 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 (NPCX_DCR(mdl) > NPCX_CTR(mdl)) 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 */ 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. */ 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);