| /* |
| * 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_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; |
| |
| /* 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, |
| SECONDARY, CONFIG_PWM_PCA9685_INIT_PRIORITY); |
| |
| #endif /* CONFIG_PWM_PCA9685_0 */ |