static int tpu_pwm_config()

in pwm-renesas-tpu.c [244:325]


static int tpu_pwm_config(struct pwm_chip *chip, struct pwm_device *_pwm,
			  int duty_ns, int period_ns)
{
	static const unsigned int prescalers[] = { 1, 4, 16, 64 };
	struct tpu_pwm_device *pwm = pwm_get_chip_data(_pwm);
	struct tpu_device *tpu = to_tpu_device(chip);
	unsigned int prescaler;
	bool duty_only = false;
	u32 clk_rate;
	u32 period;
	u32 duty;
	int ret;

	/*
	 * Pick a prescaler to avoid overflowing the counter.
	 * TODO: Pick the highest acceptable prescaler.
	 */
	clk_rate = clk_get_rate(tpu->clk);

	for (prescaler = 0; prescaler < ARRAY_SIZE(prescalers); ++prescaler) {
		period = clk_rate / prescalers[prescaler]
		       / (NSEC_PER_SEC / period_ns);
		if (period <= 0xffff)
			break;
	}

	if (prescaler == ARRAY_SIZE(prescalers) || period == 0) {
		dev_err(&tpu->pdev->dev, "clock rate mismatch\n");
		return -ENOTSUPP;
	}

	if (duty_ns) {
		duty = clk_rate / prescalers[prescaler]
		     / (NSEC_PER_SEC / duty_ns);
		if (duty > period)
			return -EINVAL;
	} else {
		duty = 0;
	}

	dev_dbg(&tpu->pdev->dev,
		"rate %u, prescaler %u, period %u, duty %u\n",
		clk_rate, prescalers[prescaler], period, duty);

	if (pwm->prescaler == prescaler && pwm->period == period)
		duty_only = true;

	pwm->prescaler = prescaler;
	pwm->period = period;
	pwm->duty = duty;

	/* If the channel is disabled we're done. */
	if (!pwm_is_enabled(_pwm))
		return 0;

	if (duty_only && pwm->timer_on) {
		/*
		 * If only the duty cycle changed and the timer is already
		 * running, there's no need to reconfigure it completely, Just
		 * modify the duty cycle.
		 */
		tpu_pwm_write(pwm, TPU_TGRAn, pwm->duty);
		dev_dbg(&tpu->pdev->dev, "%u: TGRA 0x%04x\n", pwm->channel,
			pwm->duty);
	} else {
		/* Otherwise perform a full reconfiguration. */
		ret = tpu_pwm_timer_start(pwm);
		if (ret < 0)
			return ret;
	}

	if (duty == 0 || duty == period) {
		/*
		 * To avoid running the timer when not strictly required, handle
		 * 0% and 100% duty cycles as fixed levels and stop the timer.
		 */
		tpu_pwm_set_pin(pwm, duty ? TPU_PIN_ACTIVE : TPU_PIN_INACTIVE);
		tpu_pwm_timer_stop(pwm);
	}

	return 0;
}