/* 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 IT83xx. */

#include "clock.h"
#include "fan.h"
#include "gpio.h"
#include "hooks.h"
#include "pwm.h"
#include "pwm_chip.h"
#include "registers.h"
#include "util.h"
#include "math_util.h"

#define PWM_CTRX_MIN 120
#define PWM_EC_FREQ  8000000

const struct pwm_ctrl_t pwm_ctrl_regs[] = {
	{ &IT83XX_PWM_DCR0, &IT83XX_PWM_PCSSGL, &IT83XX_GPIO_GPCRA0},
	{ &IT83XX_PWM_DCR1, &IT83XX_PWM_PCSSGL, &IT83XX_GPIO_GPCRA1},
	{ &IT83XX_PWM_DCR2, &IT83XX_PWM_PCSSGL, &IT83XX_GPIO_GPCRA2},
	{ &IT83XX_PWM_DCR3, &IT83XX_PWM_PCSSGL, &IT83XX_GPIO_GPCRA3},
	{ &IT83XX_PWM_DCR4, &IT83XX_PWM_PCSSGH, &IT83XX_GPIO_GPCRA4},
	{ &IT83XX_PWM_DCR5, &IT83XX_PWM_PCSSGH, &IT83XX_GPIO_GPCRA5},
	{ &IT83XX_PWM_DCR6, &IT83XX_PWM_PCSSGH, &IT83XX_GPIO_GPCRA6},
	{ &IT83XX_PWM_DCR7, &IT83XX_PWM_PCSSGH, &IT83XX_GPIO_GPCRA7},
};

const struct pwm_ctrl_t2 pwm_clock_ctrl_regs[] = {
	{ &IT83XX_PWM_CTR, &IT83XX_PWM_C0CPRS, &IT83XX_PWM_C0CPRS,
		&IT83XX_PWM_PCFSR, 0x01},
	{ &IT83XX_PWM_CTR1, &IT83XX_PWM_C4CPRS, &IT83XX_PWM_C4MCPRS,
		&IT83XX_PWM_PCFSR, 0x02},
	{ &IT83XX_PWM_CTR2, &IT83XX_PWM_C6CPRS, &IT83XX_PWM_C6MCPRS,
		&IT83XX_PWM_PCFSR, 0x04},
	{ &IT83XX_PWM_CTR3, &IT83XX_PWM_C7CPRS, &IT83XX_PWM_C7MCPRS,
		&IT83XX_PWM_PCFSR, 0x08},
};

static int pwm_get_cycle_time(enum pwm_channel ch)
{
	int pcs_shift;
	int pcs_mask;
	int pcs_reg;
	int cycle_time_setting;

	/* pwm channel mapping */
	ch = pwm_channels[ch].channel;

	/* bit shift for "Prescaler Clock Source Select Group" register. */
	pcs_shift = (ch % 4) * 2;

	/* setting of "Prescaler Clock Source Select Group" register. */
	pcs_reg = *pwm_ctrl_regs[ch].pwm_clock_source;

	/* only bit0 bit1 information. */
	pcs_mask = (pcs_reg >> pcs_shift) & 0x03;

	/* get cycle time setting of PWM channel x. */
	cycle_time_setting = *pwm_clock_ctrl_regs[pcs_mask].pwm_cycle_time;

	return cycle_time_setting;
}

void pwm_enable(enum pwm_channel ch, int enabled)
{
	/* pwm channel mapping */
	ch = pwm_channels[ch].channel;

	/*
	 * enabled : pin to PWM function.
	 * disabled : pin to GPIO input function.
	 */
	if (enabled)
		*pwm_ctrl_regs[ch].pwm_pin = 0x00;
	else
		*pwm_ctrl_regs[ch].pwm_pin = 0x80 |
			((pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW) ?
			4 : 2);
}

int pwm_get_enabled(enum pwm_channel ch)
{
	/* pwm channel mapping */
	ch = pwm_channels[ch].channel;

	/* pin is PWM function and PWMs clock counter was enabled */
	return ((*pwm_ctrl_regs[ch].pwm_pin & ~0x04) == 0x00 &&
		IT83XX_PWM_ZTIER & 0x02) ? 1 : 0;
}

void pwm_set_duty(enum pwm_channel ch, int percent)
{
	int pcs_shift;
	int pcs_mask;
	int pcs_reg;
	int cycle_time_setting;

	if (percent < 0)
		percent = 0;
	else if (percent > 100)
		percent = 100;

	if (pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW)
		percent = 100 - percent;

	/* pwm channel mapping */
	ch = pwm_channels[ch].channel;

	/* bit shift for "Prescaler Clock Source Select Group" register. */
	pcs_shift = (ch % 4) * 2;

	/* setting of "Prescaler Clock Source Select Group" register.*/
	pcs_reg = *pwm_ctrl_regs[ch].pwm_clock_source;

	/* only bit0 bit1 information. */
	pcs_mask = (pcs_reg >> pcs_shift) & 0x03;

	/* get cycle time setting of PWM channel x. */
	cycle_time_setting = *pwm_clock_ctrl_regs[pcs_mask].pwm_cycle_time;

	/* to update PWM DCRx depend on CTRx setting. */
	if (percent == 100) {
		*pwm_ctrl_regs[ch].pwm_duty = cycle_time_setting;
	} else {
		*pwm_ctrl_regs[ch].pwm_duty =
			((cycle_time_setting + 1) * percent) / 100;
	}
}

int pwm_get_duty(enum pwm_channel ch)
{
	int pcs_mask;
	int pcs_reg;
	int cycle_time_setting;
	int percent;
	int ch_idx;

	ch_idx = ch;

	/* pwm channel mapping */
	ch = pwm_channels[ch].channel;

	/* setting of "Prescaler Clock Source Select Group" register.*/
	pcs_reg = *pwm_ctrl_regs[ch].pwm_clock_source;

	/* only bit0 bit1 information. */
	pcs_mask = (pcs_reg >> ((ch % 4) * 2)) & 0x03;

	/* get cycle time setting of PWM channel x. */
	cycle_time_setting = *pwm_clock_ctrl_regs[pcs_mask].pwm_cycle_time;

	percent = *pwm_ctrl_regs[ch].pwm_duty * 100 / cycle_time_setting;

	if (pwm_channels[ch_idx].flags & PWM_CONFIG_ACTIVE_LOW)
		percent = 100 - percent;

	/* output signal duty cycle. */
	return percent;
}

void pwm_duty_inc(enum pwm_channel ch)
{
	int cycle_time, pwm_ch;

	/* pwm channel mapping */
	pwm_ch = pwm_channels[ch].channel;

	cycle_time = pwm_get_cycle_time(ch);

	if (pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW) {
		if (*pwm_ctrl_regs[pwm_ch].pwm_duty > 0)
			*pwm_ctrl_regs[pwm_ch].pwm_duty -= 1;
	} else {
		if (*pwm_ctrl_regs[pwm_ch].pwm_duty < cycle_time)
			*pwm_ctrl_regs[pwm_ch].pwm_duty += 1;
	}
}

void pwm_duty_reduce(enum pwm_channel ch)
{
	int cycle_time, pwm_ch;

	/* pwm channel mapping */
	pwm_ch = pwm_channels[ch].channel;

	cycle_time = pwm_get_cycle_time(ch);

	if (pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW) {
		if (*pwm_ctrl_regs[pwm_ch].pwm_duty < cycle_time)
			*pwm_ctrl_regs[pwm_ch].pwm_duty += 1;
	} else {
		if (*pwm_ctrl_regs[pwm_ch].pwm_duty > 0)
			*pwm_ctrl_regs[pwm_ch].pwm_duty -= 1;
	}
}

static int pwm_ch_freq(enum pwm_channel ch)
{
	int actual_freq = -1, targe_freq, deviation;
	int pcfsr, ctr, pcfsr_sel, pcs_shift, pcs_mask;

	targe_freq = pwm_channels[ch].freq_hz;
	deviation = (targe_freq / 100) + 1;

	for (ctr = 0xFF; ctr > PWM_CTRX_MIN; ctr--) {
		pcfsr = (PWM_EC_FREQ / (ctr + 1) / targe_freq) - 1;
		if (pcfsr >= 0) {
			actual_freq = PWM_EC_FREQ / (ctr + 1) / (pcfsr + 1);
			if (ABS(actual_freq - targe_freq) < deviation)
				break;
		}
	}

	if (ctr < PWM_CTRX_MIN) {
		actual_freq = -1;
	} else {
		pcfsr_sel = pwm_channels[ch].pcfsr_sel;
		*pwm_clock_ctrl_regs[pcfsr_sel].pwm_cycle_time = ctr;
		/* ec clock 8MHz */
		*pwm_clock_ctrl_regs[pcfsr_sel].pwm_pcfsr_reg |=
			pwm_clock_ctrl_regs[pcfsr_sel].pwm_pcfsr_ctrl;

		/* pwm channel mapping */
		ch = pwm_channels[ch].channel;

		/*
		 * bit shift for "Prescaler Clock Source Select Group"
		 * register.
		 */
		pcs_shift = (ch % 4) * 2;
		pcs_mask = pcfsr_sel << pcs_shift;

		*pwm_ctrl_regs[ch].pwm_clock_source &= ~(0x3 << pcs_shift);
		*pwm_ctrl_regs[ch].pwm_clock_source |= pcs_mask;

		*pwm_clock_ctrl_regs[pcfsr_sel].pwm_cpr_lsb = pcfsr & 0xFF;
		*pwm_clock_ctrl_regs[pcfsr_sel].pwm_cpr_msb =
			(pcfsr >> 8) & 0xFF;
	}

	return actual_freq;
}

static void pwm_init(void)
{
	int ch;

	for (ch = 0; ch < PWM_CH_COUNT; ch++)
		pwm_ch_freq(ch);

	/* enable PWMs clock counter. */
	IT83XX_PWM_ZTIER |= 0x02;
}

/* The chip PWM module initialization. */
DECLARE_HOOK(HOOK_INIT, pwm_init, HOOK_PRIO_INIT_PWM);
