| /* |
| * 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); |