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