1 /*
2  * Author: Thomas Ingleby <thomas.c.ingleby@intel.com>
3  * Author: Brendan Le Foll <brendan.le.foll@intel.com>
4  * Copyright (c) 2014, 2015 Intel Corporation.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 
26 #include <stdlib.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <limits.h>
30 
31 #include "pwm.h"
32 #include "mraa_internal.h"
33 
34 #define MAX_SIZE 64
35 #define SYSFS_PWM "/sys/class/pwm"
36 
37 static int
mraa_pwm_setup_duty_fp(mraa_pwm_context dev)38 mraa_pwm_setup_duty_fp(mraa_pwm_context dev)
39 {
40     char bu[MAX_SIZE];
41     snprintf(bu, MAX_SIZE, "/sys/class/pwm/pwmchip%d/pwm%d/duty_cycle", dev->chipid, dev->pin);
42 
43     dev->duty_fp = open(bu, O_RDWR);
44     if (dev->duty_fp == -1) {
45         return 1;
46     }
47     return 0;
48 }
49 
50 static mraa_result_t
mraa_pwm_write_period(mraa_pwm_context dev,int period)51 mraa_pwm_write_period(mraa_pwm_context dev, int period)
52 {
53     if (IS_FUNC_DEFINED(dev, pwm_period_replace)) {
54         mraa_result_t result = dev->advance_func->pwm_period_replace(dev, period);
55         if (result == MRAA_SUCCESS) {
56             dev->period = period;
57         }
58         return result;
59     }
60     char bu[MAX_SIZE];
61     snprintf(bu, MAX_SIZE, "/sys/class/pwm/pwmchip%d/pwm%d/period", dev->chipid, dev->pin);
62 
63     int period_f = open(bu, O_RDWR);
64     if (period_f == -1) {
65         syslog(LOG_ERR, "pwm: Failed to open period for writing");
66         return MRAA_ERROR_INVALID_RESOURCE;
67     }
68     char out[MAX_SIZE];
69     int length = snprintf(out, MAX_SIZE, "%d", period);
70     if (write(period_f, out, length * sizeof(char)) == -1) {
71         close(period_f);
72         return MRAA_ERROR_INVALID_RESOURCE;
73     }
74 
75     close(period_f);
76     dev->period = period;
77     return MRAA_SUCCESS;
78 }
79 
80 static mraa_result_t
mraa_pwm_write_duty(mraa_pwm_context dev,int duty)81 mraa_pwm_write_duty(mraa_pwm_context dev, int duty)
82 {
83     if (dev->duty_fp == -1) {
84         if (mraa_pwm_setup_duty_fp(dev) == 1) {
85             return MRAA_ERROR_INVALID_HANDLE;
86         }
87     }
88     char bu[64];
89     int length = sprintf(bu, "%d", duty);
90     if (write(dev->duty_fp, bu, length * sizeof(char)) == -1)
91         return MRAA_ERROR_INVALID_RESOURCE;
92     return MRAA_SUCCESS;
93 }
94 
95 static int
mraa_pwm_read_period(mraa_pwm_context dev)96 mraa_pwm_read_period(mraa_pwm_context dev)
97 {
98     char bu[MAX_SIZE];
99     char output[MAX_SIZE];
100     snprintf(bu, MAX_SIZE, "/sys/class/pwm/pwmchip%d/pwm%d/period", dev->chipid, dev->pin);
101 
102     int period_f = open(bu, O_RDWR);
103     if (period_f == -1) {
104         syslog(LOG_ERR, "pwm: Failed to open period for reading");
105         return 0;
106     }
107     off_t size = lseek(period_f, 0, SEEK_END);
108     lseek(period_f, 0, SEEK_SET);
109 
110     ssize_t rb = read(period_f, output, size + 1);
111     close(period_f);
112 
113     if (rb < 0) {
114         syslog(LOG_ERR, "pwm: Error in reading period");
115         return -1;
116     }
117 
118     char* endptr;
119     long int ret = strtol(output, &endptr, 10);
120     if ('\0' != *endptr && '\n' != *endptr) {
121         syslog(LOG_ERR, "pwm: Error in string conversion");
122         return -1;
123     } else if (ret > INT_MAX || ret < INT_MIN) {
124         syslog(LOG_ERR, "pwm: Number is invalid");
125         return -1;
126     }
127     dev->period = (int) ret;
128     return (int) ret;
129 }
130 
131 static int
mraa_pwm_read_duty(mraa_pwm_context dev)132 mraa_pwm_read_duty(mraa_pwm_context dev)
133 {
134     if (dev->duty_fp == -1) {
135         if (mraa_pwm_setup_duty_fp(dev) == 1) {
136             return MRAA_ERROR_INVALID_HANDLE;
137         }
138     } else {
139         lseek(dev->duty_fp, 0, SEEK_SET);
140     }
141     off_t size = lseek(dev->duty_fp, 0, SEEK_END);
142     lseek(dev->duty_fp, 0, SEEK_SET);
143     char output[MAX_SIZE];
144     ssize_t rb = read(dev->duty_fp, output, size + 1);
145     if (rb < 0) {
146         syslog(LOG_ERR, "pwm: Error in reading duty");
147         return -1;
148     }
149 
150     char* endptr;
151     long int ret = strtol(output, &endptr, 10);
152     if ('\0' != *endptr && '\n' != *endptr) {
153         syslog(LOG_ERR, "pwm: Error in string converstion");
154         return -1;
155     } else if (ret > INT_MAX || ret < INT_MIN) {
156         syslog(LOG_ERR, "pwm: Number is invalid");
157         return -1;
158     }
159     return (int) ret;
160 }
161 
162 static mraa_pwm_context
mraa_pwm_init_internal(mraa_adv_func_t * func_table,int chipin,int pin)163 mraa_pwm_init_internal(mraa_adv_func_t* func_table, int chipin, int pin)
164 {
165     mraa_pwm_context dev = (mraa_pwm_context) calloc(1,sizeof(struct _pwm));
166     if (dev == NULL) {
167         return NULL;
168     }
169     dev->duty_fp = -1;
170     dev->chipid = chipin;
171     dev->pin = pin;
172     dev->period = -1;
173     dev->advance_func = func_table;
174 
175     return dev;
176 }
177 
178 mraa_pwm_context
mraa_pwm_init(int pin)179 mraa_pwm_init(int pin)
180 {
181     if (plat == NULL) {
182         syslog(LOG_ERR, "pwm: Platform Not Initialised");
183         return NULL;
184     }
185     if (mraa_is_sub_platform_id(pin)) {
186         syslog(LOG_NOTICE, "pwm: Using sub platform is not supported");
187         return NULL;
188     }
189     if (plat->pins[pin].capabilites.pwm != 1) {
190         syslog(LOG_ERR, "pwm: pin not capable of pwm");
191         return NULL;
192     }
193 
194     if (plat->adv_func->pwm_init_replace != NULL) {
195         return plat->adv_func->pwm_init_replace(pin);
196     }
197     if (plat->adv_func->pwm_init_pre != NULL) {
198         if (plat->adv_func->pwm_init_pre(pin) != MRAA_SUCCESS)
199             return NULL;
200     }
201 
202     if (plat->pins[pin].capabilites.gpio == 1) {
203         // This deserves more investigation
204         mraa_gpio_context mux_i;
205         mux_i = mraa_gpio_init_raw(plat->pins[pin].gpio.pinmap);
206         if (mux_i == NULL) {
207             syslog(LOG_ERR, "pwm: error in gpio->pwm transition");
208             return NULL;
209         }
210         if (mraa_gpio_dir(mux_i, MRAA_GPIO_OUT) != MRAA_SUCCESS) {
211             syslog(LOG_ERR, "pwm: error in gpio->pwm transition");
212             return NULL;
213         }
214         if (mraa_gpio_write(mux_i, 1) != MRAA_SUCCESS) {
215             syslog(LOG_ERR, "pwm: error in gpio->pwm transition");
216             return NULL;
217         }
218         if (mraa_gpio_close(mux_i) != MRAA_SUCCESS) {
219             syslog(LOG_ERR, "pwm: error in gpio->pwm transition");
220             return NULL;
221         }
222     }
223 
224     if (plat->pins[pin].pwm.mux_total > 0) {
225         if (mraa_setup_mux_mapped(plat->pins[pin].pwm) != MRAA_SUCCESS) {
226             syslog(LOG_ERR, "pwm: Failed to set-up multiplexer");
227             return NULL;
228         }
229     }
230 
231     int chip = plat->pins[pin].pwm.parent_id;
232     int pinn = plat->pins[pin].pwm.pinmap;
233 
234     if (plat->adv_func->pwm_init_post != NULL) {
235         mraa_pwm_context pret = mraa_pwm_init_raw(chip, pinn);
236         mraa_result_t ret = plat->adv_func->pwm_init_post(pret);
237         if (ret != MRAA_SUCCESS) {
238             free(pret);
239             return NULL;
240         }
241         return pret;
242     }
243     return mraa_pwm_init_raw(chip, pinn);
244 }
245 
246 mraa_pwm_context
mraa_pwm_init_raw(int chipin,int pin)247 mraa_pwm_init_raw(int chipin, int pin)
248 {
249     mraa_pwm_context dev = mraa_pwm_init_internal(plat == NULL ? NULL : plat->adv_func , chipin, pin);
250     if (dev == NULL)
251         return NULL;
252 
253     char directory[MAX_SIZE];
254     snprintf(directory, MAX_SIZE, SYSFS_PWM "/pwmchip%d/pwm%d", dev->chipid, dev->pin);
255     struct stat dir;
256     if (stat(directory, &dir) == 0 && S_ISDIR(dir.st_mode)) {
257         syslog(LOG_NOTICE, "pwm: Pin already exported, continuing");
258         dev->owner = 0; // Not Owner
259     } else {
260         char buffer[MAX_SIZE];
261         snprintf(buffer, MAX_SIZE, "/sys/class/pwm/pwmchip%d/export", dev->chipid);
262         int export_f = open(buffer, O_WRONLY);
263         if (export_f == -1) {
264             syslog(LOG_ERR, "pwm: Failed to open export for writing");
265             free(dev);
266             return NULL;
267         }
268 
269         char out[MAX_SIZE];
270         int size = snprintf(out, MAX_SIZE, "%d", dev->pin);
271         if (write(export_f, out, size * sizeof(char)) == -1) {
272             syslog(LOG_WARNING, "pwm: Failed to write to export! Potentially already enabled");
273             close(export_f);
274             free(dev);
275             return NULL;
276         }
277         dev->owner = 1;
278         mraa_pwm_period_us(dev, plat->pwm_default_period);
279         close(export_f);
280     }
281     mraa_pwm_setup_duty_fp(dev);
282     return dev;
283 }
284 
285 mraa_result_t
mraa_pwm_write(mraa_pwm_context dev,float percentage)286 mraa_pwm_write(mraa_pwm_context dev, float percentage)
287 {
288     if (dev->period == -1) {
289         if (mraa_pwm_read_period(dev) <= 0)
290             return MRAA_ERROR_NO_DATA_AVAILABLE;
291     }
292 
293     if (percentage > 1.0f) {
294         syslog(LOG_WARNING, "pwm: number greater than 1 entered, defaulting to 100%%");
295         return mraa_pwm_write_duty(dev, dev->period);
296     }
297     return mraa_pwm_write_duty(dev, percentage * dev->period);
298 }
299 
300 float
mraa_pwm_read(mraa_pwm_context dev)301 mraa_pwm_read(mraa_pwm_context dev)
302 {
303     int period = mraa_pwm_read_period(dev);
304     if (period > 0) {
305         return (mraa_pwm_read_duty(dev) / (float) period);
306     }
307     return 0.0f;
308 }
309 
310 mraa_result_t
mraa_pwm_period(mraa_pwm_context dev,float seconds)311 mraa_pwm_period(mraa_pwm_context dev, float seconds)
312 {
313     return mraa_pwm_period_ms(dev, seconds * 1000);
314 }
315 
316 mraa_result_t
mraa_pwm_period_ms(mraa_pwm_context dev,int ms)317 mraa_pwm_period_ms(mraa_pwm_context dev, int ms)
318 {
319     return mraa_pwm_period_us(dev, ms * 1000);
320 }
321 
322 mraa_result_t
mraa_pwm_period_us(mraa_pwm_context dev,int us)323 mraa_pwm_period_us(mraa_pwm_context dev, int us)
324 {
325     if (us < plat->pwm_min_period || us > plat->pwm_max_period) {
326         syslog(LOG_ERR, "pwm: period value outside platform range");
327         return MRAA_ERROR_INVALID_PARAMETER;
328     }
329     return mraa_pwm_write_period(dev, us * 1000);
330 }
331 
332 mraa_result_t
mraa_pwm_pulsewidth(mraa_pwm_context dev,float seconds)333 mraa_pwm_pulsewidth(mraa_pwm_context dev, float seconds)
334 {
335     return mraa_pwm_pulsewidth_ms(dev, seconds * 1000);
336 }
337 
338 mraa_result_t
mraa_pwm_pulsewidth_ms(mraa_pwm_context dev,int ms)339 mraa_pwm_pulsewidth_ms(mraa_pwm_context dev, int ms)
340 {
341     return mraa_pwm_pulsewidth_us(dev, ms * 1000);
342 }
343 
344 mraa_result_t
mraa_pwm_pulsewidth_us(mraa_pwm_context dev,int us)345 mraa_pwm_pulsewidth_us(mraa_pwm_context dev, int us)
346 {
347     return mraa_pwm_write_duty(dev, us * 1000);
348 }
349 
350 mraa_result_t
mraa_pwm_enable(mraa_pwm_context dev,int enable)351 mraa_pwm_enable(mraa_pwm_context dev, int enable)
352 {
353     int status;
354     if (enable != 0) {
355         status = 1;
356     } else {
357         status = enable;
358     }
359     char bu[MAX_SIZE];
360     snprintf(bu, MAX_SIZE, "/sys/class/pwm/pwmchip%d/pwm%d/enable", dev->chipid, dev->pin);
361 
362     int enable_f = open(bu, O_RDWR);
363 
364     if (enable_f == -1) {
365         syslog(LOG_ERR, "pwm: Failed to open enable for writing");
366         return MRAA_ERROR_INVALID_RESOURCE;
367     }
368     char out[2];
369     int size = snprintf(out, sizeof(out), "%d", enable);
370     if (write(enable_f, out, size * sizeof(char)) == -1) {
371         syslog(LOG_ERR, "pwm: Failed to write to enable");
372         close(enable_f);
373         return MRAA_ERROR_INVALID_RESOURCE;
374     }
375     close(enable_f);
376     return MRAA_SUCCESS;
377 }
378 
379 mraa_result_t
mraa_pwm_unexport_force(mraa_pwm_context dev)380 mraa_pwm_unexport_force(mraa_pwm_context dev)
381 {
382     char filepath[MAX_SIZE];
383     snprintf(filepath, MAX_SIZE, "/sys/class/pwm/pwmchip%d/unexport", dev->chipid);
384 
385     int unexport_f = open(filepath, O_WRONLY);
386     if (unexport_f == -1) {
387         syslog(LOG_ERR, "pwm: Failed to open unexport for writing");
388         return MRAA_ERROR_INVALID_RESOURCE;
389     }
390 
391     char out[MAX_SIZE];
392     int size = snprintf(out, MAX_SIZE, "%d", dev->pin);
393     if (write(unexport_f, out, size * sizeof(char)) == -1) {
394         syslog(LOG_ERR, "pwm: Failed to write to unexport");
395         close(unexport_f);
396         return MRAA_ERROR_INVALID_RESOURCE;
397     }
398 
399     close(unexport_f);
400     return MRAA_SUCCESS;
401 }
402 
403 mraa_result_t
mraa_pwm_unexport(mraa_pwm_context dev)404 mraa_pwm_unexport(mraa_pwm_context dev)
405 {
406     mraa_pwm_enable(dev, 0);
407     if (dev->owner) {
408         return mraa_pwm_unexport_force(dev);
409     }
410     return MRAA_ERROR_INVALID_RESOURCE;
411 }
412 
413 mraa_result_t
mraa_pwm_close(mraa_pwm_context dev)414 mraa_pwm_close(mraa_pwm_context dev)
415 {
416     mraa_pwm_unexport(dev);
417     free(dev);
418     return MRAA_SUCCESS;
419 }
420 
421 mraa_result_t
mraa_pwm_owner(mraa_pwm_context dev,mraa_boolean_t owner_new)422 mraa_pwm_owner(mraa_pwm_context dev, mraa_boolean_t owner_new)
423 {
424     if (dev == NULL)
425         return MRAA_ERROR_INVALID_RESOURCE;
426     dev->owner = owner_new;
427     return MRAA_SUCCESS;
428 }
429 
430 mraa_result_t
mraa_pwm_config_ms(mraa_pwm_context dev,int ms,float ms_float)431 mraa_pwm_config_ms(mraa_pwm_context dev, int ms, float ms_float)
432 {
433     int old_dutycycle, old_period, status;
434     old_dutycycle = mraa_pwm_read_duty(dev);
435     old_period = mraa_pwm_read_period(dev);
436     status = mraa_pwm_period_us(dev, ms * 1000);
437     if (status != MRAA_SUCCESS) {
438         mraa_pwm_write_duty(dev, old_dutycycle);
439         return status;
440     }
441     status = mraa_pwm_write_duty(dev, 0);
442     if (status != MRAA_SUCCESS) {
443         return status;
444     }
445     status = mraa_pwm_pulsewidth_us(dev, ms_float * 1000);
446     if (status != MRAA_SUCCESS) {
447         mraa_pwm_write_duty(dev, old_dutycycle);
448         mraa_pwm_write_period(dev, old_period);
449         return status;
450     }
451     return MRAA_SUCCESS;
452 }
453 
454 mraa_result_t
mraa_pwm_config_percent(mraa_pwm_context dev,int ms,float percentage)455 mraa_pwm_config_percent(mraa_pwm_context dev, int ms, float percentage)
456 {
457     int old_dutycycle, old_period, status;
458     old_dutycycle = mraa_pwm_read_duty(dev);
459     old_period = mraa_pwm_read_period(dev);
460     status = mraa_pwm_period_us(dev, ms * 1000);
461     if (status != MRAA_SUCCESS) {
462         mraa_pwm_write_duty(dev, old_dutycycle);
463         return status;
464     }
465     status = mraa_pwm_write_duty(dev, 0);
466     if (status != MRAA_SUCCESS) {
467         return status;
468     }
469     status = mraa_pwm_pulsewidth_us(dev, (ms * 1000) * percentage);
470     if (status != MRAA_SUCCESS) {
471         mraa_pwm_write_duty(dev, old_dutycycle);
472         mraa_pwm_write_period(dev, old_period);
473         return status;
474     }
475     return MRAA_SUCCESS;
476 }
477 
478 int
mraa_pwm_get_max_period()479 mraa_pwm_get_max_period()
480 {
481     if (plat == NULL) {
482         return -1;
483     }
484     return plat->pwm_max_period;
485 }
486 
487 int
mraa_pwm_get_min_period()488 mraa_pwm_get_min_period()
489 {
490     if (plat == NULL) {
491         return -1;
492     }
493     return plat->pwm_min_period;
494 }
495