zephyr/drivers/pwm/pwm_pca9685.c
Jaap Versteegh 54c62232fc drivers: pwm: pwm_pca9685 set_pre_scale when not in restart mode
The pca9685 driver assumes the chip will be in "restart" mode
after putting it to sleep. This is not necessarily the case, which
can cause setting the prescaler to fail.
This fix allows the precaler to always be set and only restart the pwm's
when the chip was actually in restart mode after being put to sleep.

Signed-off-by: Jaap Versteegh <j.r.versteegh@gmail.com>
2023-11-02 09:44:58 +01:00

290 lines
6.9 KiB
C

/*
* Copyright (c) 2022 Nick Ward <nix.ward@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_pca9685_pwm
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(pwm_pca9685, CONFIG_PWM_LOG_LEVEL);
#define OSC_CLOCK_MAX 50000000
#define CHANNEL_CNT 16
#define ADDR_MODE1 0x00
#define SLEEP BIT(4)
#define AUTO_INC BIT(5)
#define RESTART BIT(7)
#define ADDR_MODE2 0x01
#define OUTDRV_TOTEM_POLE BIT(2)
#define OCH_ON_ACK BIT(3)
#define ADDR_LED0_ON_L 0x06
#define ADDR_LED_ON_L(n) (ADDR_LED0_ON_L + (4 * n))
#define LED_FULL_ON BIT(4)
#define LED_FULL_OFF BIT(4)
#define BYTE_N(word, n) ((word >> (8 * n)) & 0xFF)
#define ADDR_PRE_SCALE 0xFE
/* See PCA9585 datasheet Rev. 4 - 16 April 2015 section 7.3.5 */
#define OSC_CLOCK_INTERNAL 25000000
#define PWM_STEPS 4096
#define PRE_SCALE_MIN 0x03
#define PRE_SCALE_MAX 0xFF
#define PRE_SCALE_DEFAULT 0x1E
#define PWM_PERIOD_COUNT_PS(pre_scale) (PWM_STEPS * (pre_scale + 1))
/*
* When using the internal oscillator this corresponds to an
* update rate of 1526 Hz
*/
#define PWM_PERIOD_COUNT_MIN PWM_PERIOD_COUNT_PS(PRE_SCALE_MIN)
/*
* When using the internal oscillator this corresponds to an
* update rate of 24 Hz
*/
#define PWM_PERIOD_COUNT_MAX PWM_PERIOD_COUNT_PS(PRE_SCALE_MAX)
/*
* When using the internal oscillator this corresponds to an
* update rate of 200 Hz
*/
#define PWM_PERIOD_COUNT_DEFAULT PWM_PERIOD_COUNT_PS(PRE_SCALE_DEFAULT)
/*
* Time allowed for the oscillator to stabilize after the PCA9685's
* RESTART mode is used to wake the chip from SLEEP.
* See PCA9685 datasheet section 7.3.1.1
*/
#define OSCILLATOR_STABILIZE K_USEC(500)
struct pca9685_config {
struct i2c_dt_spec i2c;
bool outdrv_open_drain;
bool och_on_ack;
bool invrt;
};
struct pca9685_data {
struct k_mutex mutex;
uint8_t pre_scale;
};
static int set_reg(const struct device *dev, uint8_t addr, uint8_t value)
{
const struct pca9685_config *config = dev->config;
uint8_t buf[] = {addr, value};
int ret;
ret = i2c_write_dt(&config->i2c, buf, sizeof(buf));
if (ret != 0) {
LOG_ERR("I2C write [0x%02X]=0x%02X: %d", addr, value, ret);
} else {
LOG_DBG("[0x%02X]=0x%02X", addr, value);
}
return ret;
}
static int get_reg(const struct device *dev, uint8_t addr, uint8_t *value)
{
const struct pca9685_config *config = dev->config;
int ret;
ret = i2c_write_read_dt(&config->i2c, &addr, sizeof(addr), value,
sizeof(*value));
if (ret != 0) {
LOG_ERR("I2C write [0x%02X]=0x%02X: %d", addr, *value, ret);
}
return ret;
}
static int set_pre_scale(const struct device *dev, uint8_t value)
{
struct pca9685_data *data = dev->data;
uint8_t mode1;
int ret;
uint8_t restart = RESTART;
k_mutex_lock(&data->mutex, K_FOREVER);
/* Unblock write to PRE_SCALE by setting SLEEP bit to logic 1 */
ret = set_reg(dev, ADDR_MODE1, AUTO_INC | SLEEP);
if (ret != 0) {
goto out;
}
ret = get_reg(dev, ADDR_MODE1, &mode1);
if (ret != 0) {
goto out;
}
if ((mode1 & RESTART) == 0x00) {
restart = 0;
}
ret = set_reg(dev, ADDR_PRE_SCALE, value);
if (ret != 0) {
goto out;
}
/* Clear SLEEP bit - See datasheet section 7.3.1.1 */
ret = set_reg(dev, ADDR_MODE1, AUTO_INC);
if (ret != 0) {
goto out;
}
k_sleep(OSCILLATOR_STABILIZE);
ret = set_reg(dev, ADDR_MODE1, AUTO_INC | restart);
if (ret != 0) {
goto out;
}
out:
k_mutex_unlock(&data->mutex);
return ret;
}
static int pca9685_set_cycles(const struct device *dev,
uint32_t channel, uint32_t period_count,
uint32_t pulse_count, pwm_flags_t flags)
{
const struct pca9685_config *config = dev->config;
struct pca9685_data *data = dev->data;
uint8_t buf[5] = { 0 };
uint32_t led_off_count;
int32_t pre_scale;
int ret;
ARG_UNUSED(flags);
if (channel >= CHANNEL_CNT) {
LOG_WRN("channel out of range: %u", channel);
return -EINVAL;
}
pre_scale = DIV_ROUND_UP((int64_t)period_count, PWM_STEPS) - 1;
if (pre_scale < PRE_SCALE_MIN) {
LOG_ERR("period_count %u < %u (min)", period_count,
PWM_PERIOD_COUNT_MIN);
return -ENOTSUP;
} else if (pre_scale > PRE_SCALE_MAX) {
LOG_ERR("period_count %u > %u (max)", period_count,
PWM_PERIOD_COUNT_MAX);
return -ENOTSUP;
}
/* Only one output frequency - simple conversion to equivalent PWM */
if (pre_scale != data->pre_scale) {
data->pre_scale = pre_scale;
ret = set_pre_scale(dev, pre_scale);
if (ret != 0) {
return ret;
}
}
/* Adjust PWM output for the resolution of the PCA9685 */
led_off_count = DIV_ROUND_UP(pulse_count * PWM_STEPS, period_count);
buf[0] = ADDR_LED_ON_L(channel);
if (led_off_count == 0) {
buf[4] = LED_FULL_OFF;
} else if (led_off_count < PWM_STEPS) {
/* LED turns on at count 0 */
buf[3] = BYTE_N(led_off_count, 0);
buf[4] = BYTE_N(led_off_count, 1);
} else {
buf[2] = LED_FULL_ON;
}
return i2c_write_dt(&config->i2c, buf, sizeof(buf));
}
static int pca9685_get_cycles_per_sec(const struct device *dev,
uint32_t channel, uint64_t *cycles)
{
ARG_UNUSED(dev);
ARG_UNUSED(channel);
*cycles = OSC_CLOCK_INTERNAL;
return 0;
}
static const struct pwm_driver_api pca9685_api = {
.set_cycles = pca9685_set_cycles,
.get_cycles_per_sec = pca9685_get_cycles_per_sec,
};
static int pca9685_init(const struct device *dev)
{
const struct pca9685_config *config = dev->config;
struct pca9685_data *data = dev->data;
uint8_t value;
int ret;
(void)k_mutex_init(&data->mutex);
if (!i2c_is_ready_dt(&config->i2c)) {
LOG_ERR("I2C device not ready");
return -ENODEV;
}
ret = set_reg(dev, ADDR_MODE1, AUTO_INC);
if (ret != 0) {
LOG_ERR("set_reg MODE1: %d", ret);
return ret;
}
value = 0x00;
if (!config->outdrv_open_drain) {
value |= OUTDRV_TOTEM_POLE;
}
if (config->och_on_ack) {
value |= OCH_ON_ACK;
}
ret = set_reg(dev, ADDR_MODE2, value);
if (ret != 0) {
LOG_ERR("set_reg MODE2: %d", ret);
return ret;
}
return 0;
}
#define PCA9685_INIT(inst) \
static const struct pca9685_config pca9685_##inst##_config = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
.outdrv_open_drain = DT_INST_PROP(inst, open_drain), \
.och_on_ack = DT_INST_PROP(inst, och_on_ack), \
.invrt = DT_INST_PROP(inst, invert), \
}; \
\
static struct pca9685_data pca9685_##inst##_data; \
\
DEVICE_DT_INST_DEFINE(inst, pca9685_init, NULL, \
&pca9685_##inst##_data, \
&pca9685_##inst##_config, POST_KERNEL, \
CONFIG_PWM_INIT_PRIORITY, \
&pca9685_api);
DT_INST_FOREACH_STATUS_OKAY(PCA9685_INIT);