You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
289 lines
6.9 KiB
289 lines
6.9 KiB
/* |
|
* 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 DEVICE_API(pwm, 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);
|
|
|