| /* |
| * Google LWIS GPIO Interface |
| * |
| * Copyright (c) 2018 Google, LLC |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME "-gpio: " fmt |
| |
| #include <linux/gpio.h> |
| #include <linux/kernel.h> |
| |
| #include "lwis_gpio.h" |
| #include "lwis_interrupt.h" |
| |
| /* debug function */ |
| void lwis_gpio_list_print(char *name, struct gpio_descs *gpios) |
| { |
| int i; |
| |
| if (IS_ERR_OR_NULL(gpios)) { |
| pr_info("name: %s error: %ld\n", name, PTR_ERR(gpios)); |
| } else { |
| pr_info("name: %s, count: %d\n", name, gpios->ndescs); |
| for (i = 0; i < gpios->ndescs; i++) { |
| pr_info("gpio number: %d\n", desc_to_gpio(gpios->desc[i])); |
| } |
| } |
| } |
| |
| struct gpio_descs *lwis_gpio_list_get(struct device *dev, const char *name) |
| { |
| /* By default, the GPIO pins are acquired but uninitialized */ |
| return devm_gpiod_get_array(dev, name, GPIOD_ASIS); |
| } |
| |
| void lwis_gpio_list_put(struct gpio_descs *gpios, struct device *dev) |
| { |
| devm_gpiod_put_array(dev, gpios); |
| } |
| |
| int lwis_gpio_list_set_output_value(struct gpio_descs *gpios, int value) |
| { |
| int i; |
| int ret; |
| |
| if (!gpios) { |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < gpios->ndescs; ++i) { |
| ret = gpiod_direction_output(gpios->desc[i], value); |
| if (ret) { |
| pr_err("Failed to set value for GPIO %d\n", i); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int lwis_gpio_list_set_output_value_raw(struct gpio_descs *gpios, int value) |
| { |
| int i; |
| int ret; |
| |
| if (!gpios) { |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < gpios->ndescs; ++i) { |
| ret = gpiod_direction_output_raw(gpios->desc[i], value); |
| if (ret) { |
| pr_err("Failed to set value for GPIO %d\n", i); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int lwis_gpio_list_set_input(struct gpio_descs *gpios) |
| { |
| int i; |
| int ret; |
| |
| if (!gpios) { |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < gpios->ndescs; ++i) { |
| ret = gpiod_direction_input(gpios->desc[i]); |
| if (ret) { |
| pr_err("Failed to set GPIO %d to input\n", i); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int lwis_gpio_list_to_irqs(struct lwis_device *lwis_dev, struct lwis_gpios_info *gpios_info, |
| char *irq_gpios_names) |
| { |
| struct gpio_descs *gpios; |
| struct lwis_interrupt_list *irq_list; |
| int i; |
| int irq; |
| |
| if (!lwis_dev || !gpios_info) { |
| return -EINVAL; |
| } |
| gpios = gpios_info->gpios; |
| if (!gpios) { |
| return 0; |
| } |
| |
| irq_list = lwis_interrupt_list_alloc(lwis_dev, gpios->ndescs); |
| if (IS_ERR(irq_list)) { |
| pr_err("Failed to allocate irq list\n"); |
| return PTR_ERR(irq_list); |
| } |
| |
| for (i = 0; i < gpios->ndescs; ++i) { |
| char *name; |
| irq = gpiod_to_irq(gpios->desc[i]); |
| if (irq < 0) { |
| pr_err("gpio to irq failed (%d)\n", irq); |
| lwis_interrupt_list_free(irq_list); |
| return irq; |
| } |
| name = irq_gpios_names + i * LWIS_MAX_NAME_STRING_LEN; |
| lwis_interrupt_get_gpio_irq(irq_list, i, name, irq); |
| } |
| |
| gpios_info->irq_list = irq_list; |
| return 0; |
| } |
| |
| struct lwis_gpios_list *lwis_gpios_list_alloc(int count) |
| { |
| struct lwis_gpios_list *list; |
| |
| /* No need to allocate if count is invalid */ |
| if (count <= 0) { |
| return ERR_PTR(-EINVAL); |
| } |
| |
| list = kmalloc(sizeof(struct lwis_gpios_list), GFP_KERNEL); |
| if (!list) { |
| pr_err("Failed to allocate gpios list\n"); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| list->gpios_info = kmalloc(count * sizeof(struct lwis_gpios_info), GFP_KERNEL); |
| if (!list->gpios_info) { |
| pr_err("Failed to allocate lwis_gpios_info instances\n"); |
| kfree(list); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| list->count = count; |
| |
| return list; |
| } |
| |
| void lwis_gpios_list_free(struct lwis_gpios_list *list) |
| { |
| if (!list) { |
| return; |
| } |
| |
| if (list->gpios_info->irq_list) { |
| lwis_interrupt_list_free(list->gpios_info->irq_list); |
| } |
| if (list->gpios_info) { |
| kfree(list->gpios_info); |
| } |
| |
| kfree(list); |
| } |
| |
| struct lwis_gpios_info *lwis_gpios_get_info_by_name(struct lwis_gpios_list *list, char *name) |
| { |
| int i; |
| |
| if (!list || !name) { |
| return ERR_PTR(-EINVAL); |
| } |
| |
| for (i = 0; i < list->count; ++i) { |
| if (!strcmp(list->gpios_info[i].name, name)) { |
| return &list->gpios_info[i]; |
| } |
| } |
| return ERR_PTR(-EINVAL); |
| } |