1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * Based on htc/flounder/lights/lights.h
19 */
20
21#define LOG_TAG "lights"
22
23#include <malloc.h>
24#include <string.h>
25#include <unistd.h>
26#include <errno.h>
27#include <cutils/log.h>
28#include <hardware/lights.h>
29#include <hardware/hardware.h>
30#include <gpio.h>
31
32/* List of supported lights */
33typedef enum {
34    NOTIFICATIONS_TYPE,
35    LIGHTS_TYPE_NUM
36} light_type_t;
37
38/* Light device data structure */
39struct light_device_ext_t {
40    /* Base device */
41    struct light_device_t base_dev;
42    /* Physical pin */
43    int pin;
44    /* Current state of the light device */
45    struct light_state_t state;
46    /* Number of device references */
47    int refs;
48    /* Synchronization attributes */
49    pthread_t flash_thread;
50    pthread_cond_t flash_cond;
51    pthread_mutex_t flash_signal_mutex;
52    pthread_mutex_t write_mutex;
53    /* Transform function to apply on value */
54    int (*transform)(int);
55};
56
57static int64_t const ONE_MS_IN_NS = 1000000LL;
58static int64_t const ONE_S_IN_NS = 1000000000LL;
59
60/*
61 * Platform version strings used to identify board versions
62 */
63static char * const EDISON_ARDUINO_PLATFORM_VERSION = "arduino";
64static char * const MINNOWBOARD_TURBOT_PLATFORM_VERSION = "Turbot";
65
66/*
67 * Pin constants
68 * Please add a pin to EDISON_ARDUINO_PINS, EDISON_MINIBOARD_PINS &
69 * MINNOWBOARD_MAX_PINS when you add a new light type
70 */
71static int const EDISON_ARDUINO_PINS[LIGHTS_TYPE_NUM] = {13};
72static int const EDISON_MINIBOARD_PINS[LIGHTS_TYPE_NUM] = {31};
73static int const MINNOWBOARD_MAX_PINS[LIGHTS_TYPE_NUM] = {21};
74static int const MINNOWBOARD_TURBOT_PINS[LIGHTS_TYPE_NUM] = {27};
75
76/*
77 * Array of light devices with write_mutex statically initialized
78 * to be able to synchronize the open_lights & close_lights functions
79 */
80struct light_device_ext_t light_devices[] = {
81    [ 0 ... (LIGHTS_TYPE_NUM - 1) ] = { .write_mutex = PTHREAD_MUTEX_INITIALIZER }
82};
83
84/*
85 * Set the GPIO value
86 * @param pin physical pin of the GPIO
87 * @param value what value to set
88 * @return 0 if success, error code otherwise
89 */
90static int set_gpio_value(int pin, int value)
91{
92    mraa_gpio_context gpio = NULL;
93    int rc = 0;
94
95    if ((value != 0) && (value != 1)) {
96        return EINVAL;
97    }
98
99    gpio = mraa_gpio_init(pin);
100    if (gpio == NULL) {
101        return EPERM;
102    }
103
104    if (mraa_gpio_dir(gpio, MRAA_GPIO_OUT) != MRAA_SUCCESS) {
105        rc = EPERM;
106        goto close_gpio;
107    }
108
109    if (mraa_gpio_write(gpio, value) != MRAA_SUCCESS) {
110        rc = EPERM;
111    }
112
113close_gpio:
114    if (mraa_gpio_close(gpio) != MRAA_SUCCESS) {
115        rc = EPERM;
116    }
117
118    return rc;
119}
120
121/*
122 * Invert value
123 * @param value what value to invert
124 * @return value inverted
125 */
126static int invert_value(int value) {
127    return value ? 0 : 1;
128}
129
130/*
131 * Get current timestamp in nanoseconds
132 * @return time in nanoseconds
133 */
134int64_t get_timestamp_monotonic()
135{
136    struct timespec ts = {0, 0};
137
138    if (!clock_gettime(CLOCK_MONOTONIC, &ts)) {
139        return ONE_S_IN_NS * ts.tv_sec + ts.tv_nsec;
140    }
141
142    return -1;
143}
144
145/*
146 * Populates a timespec data structure from a int64_t timestamp
147 * @param out what timespec to populate
148 * @param target_ns timestamp in nanoseconds
149 */
150void set_timestamp(struct timespec *out, int64_t target_ns)
151{
152    out->tv_sec  = target_ns / ONE_S_IN_NS;
153    out->tv_nsec = target_ns % ONE_S_IN_NS;
154}
155
156/*
157 * pthread routine which flashes an LED
158 * @param flash_param light device pointer
159 */
160static void * flash_routine (void *flash_param)
161{
162    struct light_device_ext_t *dev = (struct light_device_ext_t *)flash_param;
163    struct light_state_t *flash_state;
164    int color = 0, rc = 0;
165    struct timespec target_time;
166    int64_t timestamp, period;
167
168    if (dev == NULL) {
169        ALOGE("%s: Cannot flash a NULL light device", __func__);
170        return NULL;
171    }
172
173    flash_state = &dev->state;
174
175    pthread_mutex_lock(&dev->flash_signal_mutex);
176
177    color = flash_state->color;
178
179    /* Light flashing loop */
180    while (flash_state->flashMode) {
181        rc = set_gpio_value(dev->pin, color);
182        if (rc != 0) {
183            ALOGE("%s: Cannot set light color", __func__);
184            goto mutex_unlock;
185        }
186
187        timestamp = get_timestamp_monotonic();
188        if (timestamp < 0) {
189            ALOGE("%s: Cannot get time from monotonic clock", __func__);
190            goto mutex_unlock;
191        }
192
193        if (color) {
194            color = 0;
195            period = flash_state->flashOnMS * ONE_MS_IN_NS;
196        } else {
197            color = 1;
198            period = flash_state->flashOffMS * ONE_MS_IN_NS;
199        }
200
201        /* check for overflow */
202        if (timestamp > LLONG_MAX - period) {
203            ALOGE("%s: Timestamp overflow", __func__);
204            goto mutex_unlock;
205        }
206
207        timestamp += period;
208
209        /* sleep until target_time or the cond var is signaled */
210        set_timestamp(&target_time, timestamp);
211        rc = pthread_cond_timedwait(&dev->flash_cond, &dev->flash_signal_mutex, &target_time);
212        if ((rc != 0) && (rc != ETIMEDOUT)) {
213            ALOGE("%s: pthread_cond_timedwait returned an error", __func__);
214            goto mutex_unlock;
215        }
216    }
217
218mutex_unlock:
219    pthread_mutex_unlock(&dev->flash_signal_mutex);
220
221    return NULL;
222}
223
224/*
225 * Check lights flash state
226 * @param state pointer to the state to check
227 * @return 0 if success, error code otherwise
228 */
229static int check_flash_state(struct light_state_t const *state)
230{
231    int64_t ns = 0;
232
233    if ((state->flashOffMS < 0) || (state->flashOnMS < 0)) {
234        return EINVAL;
235    }
236
237    if ((state->flashOffMS == 0) && (state->flashOnMS) == 0) {
238        return EINVAL;
239    }
240
241    /* check for overflow in ns */
242    ns = state->flashOffMS * ONE_MS_IN_NS;
243    if (ns / ONE_MS_IN_NS != state->flashOffMS) {
244        return EINVAL;
245    }
246    ns = state->flashOnMS * ONE_MS_IN_NS;
247    if (ns / ONE_MS_IN_NS != state->flashOnMS) {
248        return EINVAL;
249    }
250
251    return 0;
252}
253
254/*
255 * Generic function for setting the state of the light
256 * @param base_dev light device data structure
257 * @param state what state to set
258 * @return 0 if success, error code otherwise
259 */
260static int set_light_generic(struct light_device_t *base_dev,
261        struct light_state_t const *state)
262{
263    struct light_device_ext_t *dev = (struct light_device_ext_t *)base_dev;
264    struct light_state_t *current_state;
265    int rc = 0;
266
267    if (dev == NULL) {
268        ALOGE("%s: Cannot set state for NULL device", __func__);
269        return EINVAL;
270    }
271
272    current_state = &dev->state;
273
274    pthread_mutex_lock(&dev->write_mutex);
275
276    if (dev->refs == 0) {
277        ALOGE("%s: The light device is not opened", __func__);
278        pthread_mutex_unlock(&dev->write_mutex);
279        return EINVAL;
280    }
281
282    ALOGV("%s: flashMode:%x, color:%x", __func__, state->flashMode, state->color);
283
284    if (current_state->flashMode) {
285        /* destroy flashing thread */
286        pthread_mutex_lock(&dev->flash_signal_mutex);
287        current_state->flashMode = LIGHT_FLASH_NONE;
288        pthread_cond_signal(&dev->flash_cond);
289        pthread_mutex_unlock(&dev->flash_signal_mutex);
290        pthread_join(dev->flash_thread, NULL);
291    }
292
293    *current_state = *state;
294    if (dev->transform != NULL) {
295        current_state->color = dev->transform(current_state->color);
296    }
297
298    if (current_state->flashMode) {
299        /* start flashing thread */
300        if (check_flash_state(current_state) == 0) {
301            rc = pthread_create(&dev->flash_thread, NULL,
302                    flash_routine, (void *)dev);
303            if (rc != 0) {
304                ALOGE("%s: Cannot create flashing thread", __func__);
305                current_state->flashMode = LIGHT_FLASH_NONE;
306            }
307        } else {
308            ALOGE("%s: Flash state is invalid", __func__);
309            current_state->flashMode = LIGHT_FLASH_NONE;
310        }
311    } else {
312        rc = set_gpio_value(dev->pin, current_state->color);
313        if (rc != 0) {
314            ALOGE("%s: Cannot set light color.", __func__);
315        }
316    }
317
318    pthread_mutex_unlock(&dev->write_mutex);
319
320    return rc;
321}
322
323/*
324 * Initialize light synchronization resources
325 * @param cond what condition variable to initialize
326 * @param signal_mutex what mutex (associated with the condvar) to initialize
327 * @return 0 if success, error code otherwise
328 */
329static int init_light_sync_resources(pthread_cond_t *cond,
330        pthread_mutex_t *signal_mutex)
331{
332    int rc = 0;
333    pthread_condattr_t condattr;
334
335    rc = pthread_condattr_init(&condattr);
336    if (rc != 0) {
337        ALOGE("%s: Cannot initialize the pthread condattr", __func__);
338        return rc;
339    }
340
341    rc = pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC);
342    if (rc != 0) {
343        ALOGE("%s: Cannot set the clock of condattr to monotonic", __func__);
344        goto destroy_condattr;
345    }
346
347    rc = pthread_cond_init(cond, &condattr);
348    if (rc != 0) {
349        ALOGE("%s: Cannot intialize the pthread structure", __func__);
350        goto destroy_condattr;
351    }
352
353    rc = pthread_mutex_init(signal_mutex, NULL);
354    if (rc != 0) {
355        ALOGE("%s: Cannot initialize the mutex associated with the pthread cond", __func__);
356        goto destroy_cond;
357    }
358
359    pthread_condattr_destroy(&condattr);
360    return rc;
361
362destroy_cond:
363    pthread_cond_destroy(cond);
364destroy_condattr:
365    pthread_condattr_destroy(&condattr);
366    return rc;
367}
368
369/*
370 * Free light synchronization resources
371 * @param cond what condition variable to free
372 * @param signal_mutex what mutex (associated with the condvar) to free
373 */
374static void free_light_sync_resources(pthread_cond_t *cond,
375        pthread_mutex_t *signal_mutex)
376{
377    pthread_mutex_destroy(signal_mutex);
378    pthread_cond_destroy(cond);
379}
380
381/*
382 * Close the lights module
383 * @param base_dev light device data structure
384 * @return 0 if success, error code otherwise
385 */
386static int close_lights(struct light_device_t *base_dev)
387{
388    struct light_device_ext_t *dev = (struct light_device_ext_t *)base_dev;
389    int rc = 0;
390
391    if (dev == NULL) {
392        ALOGE("%s: Cannot deallocate a NULL light device", __func__);
393        return EINVAL;
394    }
395
396    pthread_mutex_lock(&dev->write_mutex);
397
398    if (dev->refs == 0) {
399        /* the light device is not open */
400        rc = EINVAL;
401        goto mutex_unlock;
402    } else if (dev->refs > 1) {
403        goto dec_refs;
404    }
405
406    if (dev->state.flashMode) {
407        /* destroy flashing thread */
408        pthread_mutex_lock(&dev->flash_signal_mutex);
409        dev->state.flashMode = LIGHT_FLASH_NONE;
410        pthread_cond_signal(&dev->flash_cond);
411        pthread_mutex_unlock(&dev->flash_signal_mutex);
412        pthread_join(dev->flash_thread, NULL);
413    }
414
415    free_light_sync_resources(&dev->flash_cond,
416            &dev->flash_signal_mutex);
417
418dec_refs:
419    dev->refs--;
420
421mutex_unlock:
422    pthread_mutex_unlock(&dev->write_mutex);
423
424    return rc;
425}
426
427/*
428 * Module initialization routine which detects the LEDs' GPIOs
429 * @param type light device type
430 * @return 0 if success, error code otherwise
431 */
432static int init_module(int type)
433{
434    const char *platform_version = NULL;
435
436    if (type < 0 || type >= LIGHTS_TYPE_NUM) {
437        return EINVAL;
438    }
439
440    light_devices[type].transform = NULL;
441
442    switch(mraa_get_platform_type()) {
443        case MRAA_INTEL_EDISON_FAB_C:
444            platform_version = mraa_get_platform_version(MRAA_MAIN_PLATFORM_OFFSET);
445            if ((platform_version != NULL) && (strncmp(platform_version,
446                    EDISON_ARDUINO_PLATFORM_VERSION,
447                    strlen(EDISON_ARDUINO_PLATFORM_VERSION)) == 0)) {
448                light_devices[type].pin = EDISON_ARDUINO_PINS[type];
449            } else {
450                light_devices[type].pin = EDISON_MINIBOARD_PINS[type];
451            }
452            break;
453        case MRAA_INTEL_MINNOWBOARD_MAX:
454            platform_version = mraa_get_platform_version(MRAA_MAIN_PLATFORM_OFFSET);
455            if ((platform_version != NULL) && (strncmp(platform_version,
456                    MINNOWBOARD_TURBOT_PLATFORM_VERSION,
457                    strlen(MINNOWBOARD_TURBOT_PLATFORM_VERSION)) == 0)) {
458                light_devices[type].pin = MINNOWBOARD_TURBOT_PINS[type];
459                light_devices[type].transform = invert_value;
460            } else {
461                light_devices[type].pin = MINNOWBOARD_MAX_PINS[type];
462            }
463            break;
464        default:
465            ALOGE("%s: Hardware platform not supported", __func__);
466            return EINVAL;
467    }
468
469    return 0;
470}
471
472/*
473 * Open a new lights device instance by name
474 * @param module associated hw module data structure
475 * @param name lights device name
476 * @param device where to store the pointer of the allocated device
477 * @return 0 if success, error code otherwise
478 */
479static int open_lights(const struct hw_module_t *module, char const *name,
480        struct hw_device_t **device)
481{
482    struct light_device_ext_t *dev;
483    int rc = 0, type = -1;
484
485    ALOGV("%s: Opening %s lights module", __func__, name);
486
487    if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
488        type = NOTIFICATIONS_TYPE;
489    } else {
490        return EINVAL;
491    }
492
493    dev = (struct light_device_ext_t *)(light_devices + type);
494
495    pthread_mutex_lock(&dev->write_mutex);
496
497    if (dev->refs != 0) {
498        /* already opened; nothing to do */
499        goto inc_refs;
500    }
501
502    rc = init_module(type);
503    if (rc != 0) {
504        ALOGE("%s: Failed to initialize lights module", __func__);
505        goto mutex_unlock;
506    }
507
508    rc = init_light_sync_resources(&dev->flash_cond,
509                &dev->flash_signal_mutex);
510    if (rc != 0) {
511        goto mutex_unlock;
512    }
513
514    dev->base_dev.common.tag = HARDWARE_DEVICE_TAG;
515    dev->base_dev.common.version = 0;
516    dev->base_dev.common.module = (struct hw_module_t *)module;
517    dev->base_dev.common.close = (int (*)(struct hw_device_t *))close_lights;
518    dev->base_dev.set_light = set_light_generic;
519
520inc_refs:
521    dev->refs++;
522    *device = (struct hw_device_t *)dev;
523
524mutex_unlock:
525    pthread_mutex_unlock(&dev->write_mutex);
526    return rc;
527}
528
529static struct hw_module_methods_t lights_methods =
530{
531    .open =  open_lights,
532};
533
534struct hw_module_t HAL_MODULE_INFO_SYM =
535{
536    .tag = HARDWARE_MODULE_TAG,
537    .version_major = 1,
538    .version_minor = 0,
539    .id = LIGHTS_HARDWARE_MODULE_ID,
540    .name = "Edison lights module",
541    .author = "Intel",
542    .methods = &lights_methods,
543};
544