Linux Audio

Check our new training course

Loading...
/*
 * Copyright (c) 2016 Intel Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <errno.h>

#include <nanokernel.h>
#include <pwm.h>
#include <device.h>
#include <init.h>

#include "qm_pwm.h"
#include "qm_scss.h"

static int pwm_qmsi_configure(struct device *dev, int access_op,
				 uint32_t pwm, int flags)
{
	ARG_UNUSED(dev);
	ARG_UNUSED(access_op);
	ARG_UNUSED(pwm);
	ARG_UNUSED(flags);

	return 0;
}

static int __set_one_port(qm_pwm_t id, uint32_t pwm, uint32_t on, uint32_t off)
{
	qm_pwm_config_t cfg;

	/* Disable timer to prevent any output */
	qm_pwm_stop(id, pwm);

	if ((off == 0) || (on == 0)) {
		/* stop PWM if so specified */
		return 0;
	}

	/* PWM mode, user-defined count mode, timer disabled */
	cfg.mode = QM_PWM_MODE_PWM;

	/* No interrupts */
	cfg.mask_interrupt = true;
	cfg.callback = NULL;

	/* Data for the timer to stay high and low */
	cfg.hi_count = on;
	cfg.lo_count = off;

	if (qm_pwm_set_config(id, pwm, &cfg) != QM_RC_OK) {
		return -EIO;
	}
	/* Enable timer so it starts running and counting */
	qm_pwm_start(id, pwm);

	return 0;
}

/*
 * Set the duration for on/off timer of PWM.
 *
 * This sets the duration for the pin to low or high.
 *
 * Assumes a nominal system clock of 32MHz, each count of on/off represents
 * 31.25ns (e.g. on == 2 means the pin stays high for 62.5ns).
 * The duration of 1 count depends on system clock. Refer to the hardware
 * manual for more information.
 *
 * Parameters
 * dev: Device struct
 * access_op: whether to set one pin or all
 * pwm: Which PWM port to set
 * on: Duration for pin to stay high (must be >= 2)
 * off: Duration for pin to stay low (must be >= 2)
 *
 * return 0, -ENOTSUP
 */
static int pwm_qmsi_set_values(struct device *dev, int access_op,
				  uint32_t pwm, uint32_t on, uint32_t off)
{
	int i;

	switch (access_op) {
	case PWM_ACCESS_BY_PIN:
		/* make sure the PWM port exists */
		if (pwm >= CONFIG_PWM_QMSI_NUM_PORTS) {
			return -EIO;
		}
		return __set_one_port(QM_PWM_0, pwm, on, off);

	case PWM_ACCESS_ALL:
		for (i = 0; i < CONFIG_PWM_QMSI_NUM_PORTS; i++) {
			__set_one_port(QM_PWM_0, i, on, off);
		}
	break;
	default:
		return -ENOTSUP;
	}

	return 0;

}

static int pwm_qmsi_set_duty_cycle(struct device *dev, int access_op,
				 uint32_t pwm, uint8_t duty)
{
	/* The IP block does not natively support duty cycle settings.
	 * So need to use set_values().
	 */

	ARG_UNUSED(dev);
	ARG_UNUSED(access_op);
	ARG_UNUSED(pwm);
	ARG_UNUSED(duty);

	return -ENOTSUP;
}

/*
 * Set the PWM IP block suspended/low power state
 * In this case, the PWN does not support power state handling
 *
 * Parameters
 * dev: Device struct
 * return -ENOTSUP
 */
static int pwm_qmsi_suspend(struct device *dev)
{
	ARG_UNUSED(dev);

	return -ENOTSUP;
}

/*
 * Bring back the PWM IP block from suspended/low power state
 * In this case, the PWN does not support power state handling
 *
 * Parameters
 * dev: Device struct
 * return -ENOTSUP
 */
static int pwm_qmsi_resume(struct device *dev)
{
	ARG_UNUSED(dev);

	return -ENOTSUP;
}

static struct pwm_driver_api pwm_qmsi_drv_api_funcs = {
	.config = pwm_qmsi_configure,
	.set_values = pwm_qmsi_set_values,
	.set_duty_cycle = pwm_qmsi_set_duty_cycle,
	.suspend = pwm_qmsi_suspend,
	.resume = pwm_qmsi_resume,
};

static int pwm_qmsi_init(struct device *dev)
{
	dev->driver_api = &pwm_qmsi_drv_api_funcs;
	clk_periph_enable(CLK_PERIPH_PWM_REGISTER | CLK_PERIPH_CLK);
	return 0;
}

DEVICE_INIT(pwm_qmsi_0, CONFIG_PWM_QMSI_DEV_NAME, pwm_qmsi_init,
	    NULL, NULL,
	    SECONDARY, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);