| /* |
| * Copyright (c) 2015 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file Driver for PCA9685 I2C-based PWM driver. |
| */ |
| |
| #include <errno.h> |
| |
| #include <kernel.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; |
| } |
| |
| /* |
| * period_count is always taken as 4095. To control the on period send |
| * value to pulse_count |
| */ |
| static int pwm_pca9685_pin_set_cycles(struct device *dev, u32_t pwm, |
| u32_t period_count, u32_t pulse_count) |
| { |
| 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; |
| u16_t i2c_addr = config->i2c_slave_addr; |
| u8_t buf[] = { 0, 0, 0, 0, 0}; |
| |
| ARG_UNUSED(period_count); |
| if (!_has_i2c_master(dev)) { |
| return -EINVAL; |
| } |
| |
| if (pwm > MAX_PWM_OUT) { |
| return -EINVAL; |
| } |
| buf[0] = REG_LED_ON_L(pwm); |
| |
| /* If either pulse_count > max ticks, treat PWM as 100%. |
| * If pulse_count value == 0, treat it as 0%. |
| * Otherwise, populate registers accordingly. |
| */ |
| if (pulse_count >= PWM_ONE_PERIOD_TICKS) { |
| buf[1] = 0x0; |
| buf[2] = (1 << 4); |
| buf[3] = 0x0; |
| buf[4] = 0x0; |
| } else if (pulse_count == 0) { |
| buf[1] = 0x0; |
| buf[2] = 0x0; |
| buf[3] = 0x0; |
| buf[4] = (1 << 4); |
| } else { |
| /* No start delay given. When the count is 0 the |
| * pwm will be high . |
| */ |
| buf[0] = 0x0; |
| buf[1] = 0x0; |
| buf[2] = (pulse_count & 0xFF); |
| buf[3] = ((pulse_count >> 8) & 0x0F); |
| } |
| |
| return i2c_write(i2c_master, buf, sizeof(buf), i2c_addr); |
| } |
| |
| |
| static const struct pwm_driver_api pwm_pca9685_drv_api_funcs = { |
| .pin_set = pwm_pca9685_pin_set_cycles |
| }; |
| |
| /** |
| * @brief Initialization function of PCA9685 |
| * |
| * @param dev Device struct |
| * @return 0 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; |
| u8_t buf[] = {0, 0}; |
| int ret; |
| |
| /* Find out the device struct of the I2C master */ |
| i2c_master = device_get_binding((char *)config->i2c_master_dev_name); |
| if (!i2c_master) { |
| return -EINVAL; |
| } |
| 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 != 0) { |
| return -EPERM; |
| } |
| |
| dev->driver_api = &pwm_pca9685_drv_api_funcs; |
| |
| return 0; |
| } |
| |
| /* Initialization for PWM_PCA9685_0 */ |
| #ifdef CONFIG_PWM_PCA9685_0 |
| #include <device.h> |
| #include <init.h> |
| |
| static const 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; |
| |
| /* This has to init after I2C master */ |
| DEVICE_INIT(pwm_pca9685_0, CONFIG_PWM_PCA9685_0_DEV_NAME, |
| pwm_pca9685_init, |
| &pwm_pca9685_0_drvdata, &pwm_pca9685_0_cfg, |
| POST_KERNEL, CONFIG_PWM_PCA9685_INIT_PRIORITY); |
| |
| #endif /* CONFIG_PWM_PCA9685_0 */ |