static int atmel_tcb_pwm_config()

in pwm-atmel-tcb.c [290:366]


static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
				int duty_ns, int period_ns)
{
	struct atmel_tcb_pwm_chip *tcbpwmc = to_tcb_chip(chip);
	struct atmel_tcb_pwm_device *tcbpwm = pwm_get_chip_data(pwm);
	struct atmel_tcb_pwm_device *atcbpwm = NULL;
	int i = 0;
	int slowclk = 0;
	unsigned period;
	unsigned duty;
	unsigned rate = clk_get_rate(tcbpwmc->clk);
	unsigned long long min;
	unsigned long long max;

	/*
	 * Find best clk divisor:
	 * the smallest divisor which can fulfill the period_ns requirements.
	 * If there is a gclk, the first divisor is actuallly the gclk selector
	 */
	if (tcbpwmc->gclk)
		i = 1;
	for (; i < ARRAY_SIZE(atmel_tcb_divisors); ++i) {
		if (atmel_tcb_divisors[i] == 0) {
			slowclk = i;
			continue;
		}
		min = div_u64((u64)NSEC_PER_SEC * atmel_tcb_divisors[i], rate);
		max = min << tcbpwmc->width;
		if (max >= period_ns)
			break;
	}

	/*
	 * If none of the divisor are small enough to represent period_ns
	 * take slow clock (32KHz).
	 */
	if (i == ARRAY_SIZE(atmel_tcb_divisors)) {
		i = slowclk;
		rate = clk_get_rate(tcbpwmc->slow_clk);
		min = div_u64(NSEC_PER_SEC, rate);
		max = min << tcbpwmc->width;

		/* If period is too big return ERANGE error */
		if (max < period_ns)
			return -ERANGE;
	}

	duty = div_u64(duty_ns, min);
	period = div_u64(period_ns, min);

	if (pwm->hwpwm == 0)
		atcbpwm = tcbpwmc->pwms[1];
	else
		atcbpwm = tcbpwmc->pwms[0];

	/*
	 * PWM devices provided by the TCB driver are grouped by 2.
	 * PWM devices in a given group must be configured with the
	 * same period_ns.
	 *
	 * We're checking the period value of the second PWM device
	 * in this group before applying the new config.
	 */
	if ((atcbpwm && atcbpwm->duty > 0 &&
			atcbpwm->duty != atcbpwm->period) &&
		(atcbpwm->div != i || atcbpwm->period != period)) {
		dev_err(chip->dev,
			"failed to configure period_ns: PWM group already configured with a different value\n");
		return -EINVAL;
	}

	tcbpwm->period = period;
	tcbpwm->div = i;
	tcbpwm->duty = duty;

	return 0;
}