summaryrefslogtreecommitdiff
path: root/drivers/pwm/pwm_pca9685.c
blob: b586020e5028cab6ae9e50d20b18a6ae1df29f29 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/*
 * 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 <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;
}

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 0;
}

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 -EINVAL;
	}

	switch (access_op) {
	case PWM_ACCESS_BY_PIN:
		if (pwm > MAX_PWM_OUT) {
			return -EINVAL;
		}
		buf[0] = REG_LED_ON_L(pwm);
		break;
	case PWM_ACCESS_ALL:
		buf[0] = REG_ALL_LED_ON_L;
		break;
	default:
		return -ENOTSUP;
	}

	/* 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 const 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,
};

/**
 * @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;
	uint8_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 */