zephyr/drivers/pwm/pwm_pca9685.c
Johan Kruger 7a9fa51b61 pwm: Changes to PWM driver for the PCA9685.
- Changed behavior of duty cycle to have the signal ON time as the
  duty percentage instead off the off time. Note however, this requires
  that the off time is controlled. The behavior seems to be inverse of
  what the user will expect on the header IO.

Change-Id: I1e7abf0324509de375d545a0215fd1edf2283814
Work-by: Johan Kruger <johan.kruger@windriver.com>
Signed-off-by: Peter Mitsis <peter.mitsis@windriver.com>
2016-02-05 20:24:43 -05:00

241 lines
5.7 KiB
C

/*
* Copyright (c) 2015 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.
*/
/**
* @file Driver for PCA9685 I2C-based PWM driver.
*/
#include <nanokernel.h>
#include <i2c.h>
#include <pwm.h>
#include "pwm_pca9685.h"
#define REG_MODE1 0x00
#define REG_MODE2 0x01
#define REG_LED_ON_L(n) ((4 * n) + 0x06)
#define REG_LED_ON_H(n) ((4 * n) + 0x07)
#define REG_LED_OFF_L(n) ((4 * n) + 0x08)
#define REG_LED_OFF_H(n) ((4 * n) + 0x09)
#define REG_ALL_LED_ON_L 0xFA
#define REG_ALL_LED_ON_H 0xFB
#define REG_ALL_LED_OFF_L 0xFC
#define REG_ALL_LED_OFF_H 0xFD
#define REG_PRE_SCALE 0xFE
/* Maximum PWM outputs */
#define MAX_PWM_OUT 16
/* How many ticks per one period */
#define PWM_ONE_PERIOD_TICKS 4096
/**
* @brief Check to see if a I2C master is identified for communication.
*
* @param dev Device struct.
* @return 1 if I2C master is identified, 0 if not.
*/
static inline int _has_i2c_master(struct device *dev)
{
struct pwm_pca9685_drv_data * const drv_data =
(struct pwm_pca9685_drv_data * const)dev->driver_data;
struct device * const i2c_master = drv_data->i2c_master;
if (i2c_master)
return 1;
else
return 0;
}
static int pwm_pca9685_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 DEV_OK;
}
static int pwm_pca9685_set_values(struct device *dev, int access_op,
uint32_t pwm, uint32_t on, uint32_t off)
{
const struct pwm_pca9685_config * const config =
dev->config->config_info;
struct pwm_pca9685_drv_data * const drv_data =
(struct pwm_pca9685_drv_data * const)dev->driver_data;
struct device * const i2c_master = drv_data->i2c_master;
uint16_t i2c_addr = config->i2c_slave_addr;
uint8_t buf[] = { 0, 0, 0, 0, 0};
if (!_has_i2c_master(dev)) {
return DEV_INVALID_CONF;
}
switch (access_op) {
case PWM_ACCESS_BY_PIN:
if (pwm > MAX_PWM_OUT) {
return DEV_INVALID_CONF;
}
buf[0] = REG_LED_ON_L(pwm);
break;
case PWM_ACCESS_ALL:
buf[0] = REG_ALL_LED_ON_L;
break;
default:
return DEV_INVALID_OP;
}
/* If either ON and/or OFF > max ticks, treat PWM as 100%.
* If OFF value == 0, treat it as 0%.
* Otherwise, populate registers accordingly.
*/
if ((on >= PWM_ONE_PERIOD_TICKS) || (off >= PWM_ONE_PERIOD_TICKS)) {
buf[1] = 0x0;
buf[2] = (1 << 4);
buf[3] = 0x0;
buf[4] = 0x0;
} else if (off == 0) {
buf[1] = 0x0;
buf[2] = 0x0;
buf[3] = 0x0;
buf[4] = (1 << 4);
} else {
buf[1] = (on & 0xFF);
buf[2] = ((on >> 8) & 0x0F);
buf[3] = (off & 0xFF);
buf[4] = ((off >> 8) & 0x0F);
}
return i2c_polling_write(i2c_master, buf, sizeof(buf), i2c_addr);
}
/**
* Duty cycle describes the percentage of time a signal is turned
* to the ON state.
*/
static int pwm_pca9685_set_duty_cycle(struct device *dev, int access_op,
uint32_t pwm, uint8_t duty)
{
uint32_t on, off, phase;
phase = 0; /* Hard coded until API changes */
if (duty == 0) {
/* Turn off PWM */
on = 0;
off = 0;
} else if (duty >= 100) {
/* Force PWM to be 100% */
on = PWM_ONE_PERIOD_TICKS + 1;
off = PWM_ONE_PERIOD_TICKS + 1;
} else {
off = PWM_ONE_PERIOD_TICKS * duty / 100;
on = phase;
}
return pwm_pca9685_set_values(dev, access_op, pwm, on, off);
}
static int pwm_pca9685_suspend(struct device *dev)
{
if (!_has_i2c_master(dev)) {
return DEV_INVALID_CONF;
}
return DEV_INVALID_OP;
}
static int pwm_pca9685_resume(struct device *dev)
{
if (!_has_i2c_master(dev)) {
return DEV_INVALID_CONF;
}
return DEV_INVALID_OP;
}
static struct pwm_driver_api pwm_pca9685_drv_api_funcs = {
.config = pwm_pca9685_configure,
.set_values = pwm_pca9685_set_values,
.set_duty_cycle = pwm_pca9685_set_duty_cycle,
.suspend = pwm_pca9685_suspend,
.resume = pwm_pca9685_resume,
};
/**
* @brief Initialization function of PCA9685
*
* @param dev Device struct
* @return DEV_OK if successful, failed otherwise.
*/
int pwm_pca9685_init(struct device *dev)
{
const struct pwm_pca9685_config * const config =
dev->config->config_info;
struct pwm_pca9685_drv_data * const drv_data =
(struct pwm_pca9685_drv_data * const)dev->driver_data;
struct device *i2c_master;
uint8_t buf[] = {0, 0};
int ret;
dev->driver_api = &pwm_pca9685_drv_api_funcs;
/* Find out the device struct of the I2C master */
i2c_master = device_get_binding((char *)config->i2c_master_dev_name);
if (!i2c_master) {
return DEV_INVALID_CONF;
}
drv_data->i2c_master = i2c_master;
/* MODE1 register */
buf[0] = REG_MODE1;
buf[1] = (1 << 5); /* register addr auto increment */
ret = i2c_write(i2c_master, buf, 2, config->i2c_slave_addr);
if (ret != DEV_OK) {
return DEV_NOT_CONFIG;
}
return DEV_OK;
}
/* Initialization for PWM_PCA9685_0 */
#ifdef CONFIG_PWM_PCA9685_0
#include <device.h>
#include <init.h>
static struct pwm_pca9685_config pwm_pca9685_0_cfg = {
.i2c_master_dev_name = CONFIG_PWM_PCA9685_0_I2C_MASTER_DEV_NAME,
.i2c_slave_addr = CONFIG_PWM_PCA9685_0_I2C_ADDR,
};
static struct pwm_pca9685_drv_data pwm_pca9685_0_drvdata;
DECLARE_DEVICE_INIT_CONFIG(pwm_pca9685_0,
CONFIG_PWM_PCA9685_0_DEV_NAME,
pwm_pca9685_init, &pwm_pca9685_0_cfg);
/* This has to init after I2C master */
nano_early_init(pwm_pca9685_0, &pwm_pca9685_0_drvdata);
#endif /* CONFIG_PWM_PCA9685_0 */