Skip to content

Instantly share code, notes, and snippets.

@craftyguy
Created September 7, 2017 15:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save craftyguy/e87a1eb9f5fe4ecf1ac343ec5ae4a676 to your computer and use it in GitHub Desktop.
Save craftyguy/e87a1eb9f5fe4ecf1ac343ec5ae4a676 to your computer and use it in GitHub Desktop.
n900 gpio-switch patch
--- a/arch/arm/plat-omap/Kconfig
+++ b/arch/arm/plat-omap/Kconfig
@@ -63,6 +63,13 @@ config OMAP_RESET_CLOCKS
probably do not want this option enabled until your
device drivers work properly.
+config OMAP_GPIO_SWITCH
+ bool "GPIO switch support"
+ default n
+ help
+ Say Y, if you want to have support for reporting of GPIO
+ switches (e.g. cover switches) via sysfs.
+
config OMAP_MPU_TIMER
bool "Use mpu timer"
depends on ARCH_OMAP1
--- a/arch/arm/plat-omap/Makefile
+++ b/arch/arm/plat-omap/Makefile
@@ -11,3 +11,4 @@ obj-y := sram.o dma.o counter_32k.o
obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o
obj-$(CONFIG_OMAP_DEBUG_LEDS) += debug-leds.o
+obj-$(CONFIG_OMAP_GPIO_SWITCH) += gpio-switch.o
--- a/arch/arm/plat-omap/gpio-switch.c
+++ b/arch/arm/plat-omap/gpio-switch.c
@@ -0,0 +1,571 @@
+/*
+ * linux/arch/arm/plat-omap/gpio-switch.c
+ *
+ * Copyright (C) 2004-2006 Nokia Corporation
+ * Written by Juha Yrj�l� <juha.yrjola@nokia.com>
+ * and Paul Mundt <paul.mundt@nokia.com>
+ *
+ * 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.
+ */
+
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/timer.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <plat/gpio-switch.h>
+
+struct gpio_switch {
+ char name[14];
+ u16 gpio;
+ unsigned flags:4;
+ unsigned type:4;
+ unsigned state:1;
+ unsigned both_edges:1;
+
+ u16 debounce_rising;
+ u16 debounce_falling;
+
+ int disabled;
+
+ void (* notify)(void *data, int state);
+ void *notify_data;
+
+ struct work_struct work;
+ struct timer_list timer;
+ struct platform_device pdev;
+
+ struct list_head node;
+
+ bool failed;
+};
+
+static LIST_HEAD(gpio_switches);
+static struct platform_device *gpio_sw_platform_dev;
+static struct platform_driver gpio_sw_driver;
+
+static const struct omap_gpio_switch *board_gpio_sw_table;
+static int board_gpio_sw_count;
+
+static const char *cover_str[2] = { "open", "closed" };
+static const char *connection_str[2] = { "disconnected", "connected" };
+static const char *activity_str[2] = { "inactive", "active" };
+
+/*
+ * GPIO switch state default debounce delay in ms
+ */
+#define OMAP_GPIO_SW_DEFAULT_DEBOUNCE 10
+
+static const char **get_sw_str(struct gpio_switch *sw)
+{
+ switch (sw->type) {
+ case OMAP_GPIO_SWITCH_TYPE_COVER:
+ return cover_str;
+ case OMAP_GPIO_SWITCH_TYPE_CONNECTION:
+ return connection_str;
+ case OMAP_GPIO_SWITCH_TYPE_ACTIVITY:
+ return activity_str;
+ default:
+ BUG();
+ return NULL;
+ }
+}
+
+static const char *get_sw_type(struct gpio_switch *sw)
+{
+ switch (sw->type) {
+ case OMAP_GPIO_SWITCH_TYPE_COVER:
+ return "cover";
+ case OMAP_GPIO_SWITCH_TYPE_CONNECTION:
+ return "connection";
+ case OMAP_GPIO_SWITCH_TYPE_ACTIVITY:
+ return "activity";
+ default:
+ BUG();
+ return NULL;
+ }
+}
+
+static void print_sw_state(struct gpio_switch *sw, int state)
+{
+ const char **str;
+
+ str = get_sw_str(sw);
+ if (str != NULL)
+ printk(KERN_INFO "%s (GPIO %d) is now %s\n", sw->name, sw->gpio, str[state]);
+}
+
+static int gpio_sw_get_state(struct gpio_switch *sw)
+{
+ int state = 0;
+
+ if (!sw->failed)
+ state = gpio_get_value(sw->gpio);
+
+ if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED)
+ state = !state;
+
+ return state;
+}
+
+static ssize_t gpio_sw_state_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct gpio_switch *sw = dev_get_drvdata(dev);
+ const char **str;
+ char state[16];
+ int enable;
+
+ if (!(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT))
+ return -EPERM;
+
+ if (sscanf(buf, "%15s", state) != 1)
+ return -EINVAL;
+
+ str = get_sw_str(sw);
+ if (strcmp(state, str[0]) == 0)
+ sw->state = enable = 0;
+ else if (strcmp(state, str[1]) == 0)
+ sw->state = enable = 1;
+ else
+ return -EINVAL;
+
+ if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED)
+ enable = !enable;
+
+ if (!sw->failed)
+ gpio_set_value(sw->gpio, enable);
+
+ return count;
+}
+
+static ssize_t gpio_sw_state_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct gpio_switch *sw = dev_get_drvdata(dev);
+ const char **str;
+
+ if (sw->disabled)
+ sw->state = gpio_sw_get_state(sw);
+ str = get_sw_str(sw);
+ return sprintf(buf, "%s\n", str[sw->state]);
+}
+
+static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, gpio_sw_state_show,
+ gpio_sw_state_store);
+
+static ssize_t gpio_sw_type_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct gpio_switch *sw = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", get_sw_type(sw));
+}
+
+static DEVICE_ATTR(type, S_IRUGO, gpio_sw_type_show, NULL);
+
+static ssize_t gpio_sw_direction_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct gpio_switch *sw = dev_get_drvdata(dev);
+ int is_output;
+
+ is_output = sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT;
+ return sprintf(buf, "%s\n", is_output ? "output" : "input");
+}
+
+static DEVICE_ATTR(direction, S_IRUGO, gpio_sw_direction_show, NULL);
+
+static ssize_t gpio_sw_disable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct gpio_switch *sw = dev_get_drvdata(dev);
+ unsigned long res;
+
+ if (kstrtoul(buf, 10, &res) < 0)
+ return -EINVAL;
+
+ if (!!res == sw->disabled)
+ goto out;
+ sw->disabled = !!res;
+
+ if (sw->failed)
+ goto out;
+
+ if (res) {
+ disable_irq(gpio_to_irq(sw->gpio));
+ } else {
+ sw->state = gpio_sw_get_state(sw);
+ enable_irq(gpio_to_irq(sw->gpio));
+ }
+out:
+ return count;
+}
+
+static ssize_t gpio_sw_disable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct gpio_switch *sw = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", sw->disabled);
+}
+
+static DEVICE_ATTR(disable, S_IRUGO | S_IWUSR, gpio_sw_disable_show,
+ gpio_sw_disable_store);
+
+static irqreturn_t gpio_sw_irq_handler(int irq, void *arg)
+{
+ struct gpio_switch *sw = arg;
+ unsigned long timeout;
+ int state;
+
+ if (!sw->both_edges && !sw->failed) {
+ if (gpio_get_value(sw->gpio))
+ irq_set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_FALLING);
+ else
+ irq_set_irq_type(gpio_to_irq(sw->gpio), IRQ_TYPE_EDGE_RISING);
+ }
+
+ state = gpio_sw_get_state(sw);
+ if (sw->state == state)
+ return IRQ_HANDLED;
+
+ if (state)
+ timeout = sw->debounce_rising;
+ else
+ timeout = sw->debounce_falling;
+ if (!timeout)
+ schedule_work(&sw->work);
+ else
+ mod_timer(&sw->timer, jiffies + msecs_to_jiffies(timeout));
+
+ return IRQ_HANDLED;
+}
+
+static void gpio_sw_timer(unsigned long arg)
+{
+ struct gpio_switch *sw = (struct gpio_switch *) arg;
+
+ schedule_work(&sw->work);
+}
+
+static void gpio_sw_handler(struct work_struct *work)
+{
+ struct gpio_switch *sw = container_of(work, struct gpio_switch, work);
+ int state;
+
+ state = gpio_sw_get_state(sw);
+ if (sw->state == state)
+ return;
+
+ sw->state = state;
+ if (sw->notify != NULL)
+ sw->notify(sw->notify_data, state);
+ sysfs_notify(&sw->pdev.dev.kobj, NULL, "state");
+ print_sw_state(sw, state);
+}
+
+static void gpio_sw_release(struct device *dev)
+{
+}
+
+static int __init new_switch(struct gpio_switch *sw)
+{
+ int r, direction, trigger;
+
+ switch (sw->type) {
+ case OMAP_GPIO_SWITCH_TYPE_COVER:
+ case OMAP_GPIO_SWITCH_TYPE_CONNECTION:
+ case OMAP_GPIO_SWITCH_TYPE_ACTIVITY:
+ break;
+ default:
+ printk(KERN_ERR "invalid GPIO switch type: %d\n", sw->type);
+ return -EINVAL;
+ }
+
+ sw->pdev.name = sw->name;
+ sw->pdev.id = -1;
+
+ sw->pdev.dev.parent = &gpio_sw_platform_dev->dev;
+ sw->pdev.dev.driver = &gpio_sw_driver.driver;
+ sw->pdev.dev.release = gpio_sw_release;
+
+ r = platform_device_register(&sw->pdev);
+ if (r) {
+ printk(KERN_ERR "gpio-switch: platform device registration "
+ "failed for %s", sw->name);
+ return r;
+ }
+ dev_set_drvdata(&sw->pdev.dev, sw);
+
+ if (!sw->failed)
+ r = gpio_request(sw->gpio, sw->name);
+ else
+ r = -EINVAL;
+ if (r < 0) {
+ printk(KERN_ERR "gpio-switch: gpio_reguest failed for %s %d\n",
+ sw->name, sw->gpio);
+ sw->failed = true;
+ }
+
+ /* input: 1, output: 0 */
+ direction = !(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT);
+ if (!sw->failed) {
+ if (direction) {
+ gpio_direction_input(sw->gpio);
+ sw->state = gpio_sw_get_state(sw);
+ } else {
+ int state = sw->state = !!(sw->flags &
+ OMAP_GPIO_SWITCH_FLAG_OUTPUT_INIT_ACTIVE);
+
+ if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED)
+ state = !state;
+ gpio_direction_output(sw->gpio, state);
+ }
+ }
+
+ r = 0;
+ r |= device_create_file(&sw->pdev.dev, &dev_attr_state);
+ r |= device_create_file(&sw->pdev.dev, &dev_attr_type);
+ r |= device_create_file(&sw->pdev.dev, &dev_attr_direction);
+ if (direction)
+ r |= device_create_file(&sw->pdev.dev, &dev_attr_disable);
+ if (r)
+ printk(KERN_ERR "gpio-switch: attribute file creation "
+ "failed for %s\n", sw->name);
+
+ if (!direction)
+ return 0;
+
+ trigger = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
+ sw->both_edges = 1;
+
+ if (!sw->failed) {
+ r = request_irq(gpio_to_irq(sw->gpio), gpio_sw_irq_handler,
+ IRQF_SHARED | trigger, sw->name, sw);
+ if (r < 0) {
+ printk(KERN_ERR "gpio-switch: request_irq() failed "
+ "for GPIO %d\n", sw->gpio);
+ sw->failed = true;
+ gpio_free(sw->gpio);
+ }
+ }
+
+ if (!sw->failed) {
+ INIT_WORK(&sw->work, gpio_sw_handler);
+ init_timer(&sw->timer);
+
+ sw->timer.function = gpio_sw_timer;
+ sw->timer.data = (unsigned long)sw;
+ }
+
+ list_add(&sw->node, &gpio_switches);
+
+ return 0;
+}
+
+static struct gpio_switch * __init find_switch(int gpio, const char *name)
+{
+ struct gpio_switch *sw;
+
+ list_for_each_entry(sw, &gpio_switches, node) {
+ if ((gpio < 0 || sw->gpio != gpio) &&
+ (name == NULL || strcmp(sw->name, name) != 0))
+ continue;
+
+ if (gpio < 0 || name == NULL)
+ goto no_check;
+
+ if (strcmp(sw->name, name) != 0)
+ printk("gpio-switch: name mismatch for %d (%s, %s)\n",
+ gpio, name, sw->name);
+ else if (sw->gpio != gpio)
+ printk("gpio-switch: GPIO mismatch for %s (%d, %d)\n",
+ name, gpio, sw->gpio);
+no_check:
+ return sw;
+ }
+ return NULL;
+}
+
+static int __init add_board_switches(void)
+{
+ int i;
+
+ for (i = 0; i < board_gpio_sw_count; i++) {
+ const struct omap_gpio_switch *cfg;
+ struct gpio_switch *sw;
+ int r;
+
+ cfg = board_gpio_sw_table + i;
+ if (strlen(cfg->name) > sizeof(sw->name) - 1)
+ return -EINVAL;
+ /* Check whether we only update an existing switch
+ * or add a new switch. */
+ sw = find_switch(cfg->gpio, cfg->name);
+ if (sw != NULL) {
+ sw->debounce_rising = cfg->debounce_rising;
+ sw->debounce_falling = cfg->debounce_falling;
+ sw->notify = cfg->notify;
+ sw->notify_data = cfg->notify_data;
+ continue;
+ } else {
+ if (cfg->gpio < 0 || cfg->name == NULL) {
+ printk("gpio-switch: required switch not "
+ "found (%d, %s)\n", cfg->gpio,
+ cfg->name);
+ continue;
+ }
+ }
+ sw = kzalloc(sizeof(*sw), GFP_KERNEL);
+ if (sw == NULL) {
+ printk(KERN_ERR "gpio-switch: kmalloc failed\n");
+ return -ENOMEM;
+ }
+ strlcpy(sw->name, cfg->name, sizeof(sw->name));
+ sw->gpio = cfg->gpio;
+ sw->flags = cfg->flags;
+ sw->type = cfg->type;
+ sw->debounce_rising = cfg->debounce_rising;
+ sw->debounce_falling = cfg->debounce_falling;
+ sw->notify = cfg->notify;
+ sw->notify_data = cfg->notify_data;
+ sw->failed = cfg->failed;
+ if ((r = new_switch(sw)) < 0) {
+ kfree(sw);
+ }
+ }
+ return 0;
+}
+
+static void gpio_sw_cleanup(void)
+{
+ struct gpio_switch *sw = NULL, *old = NULL;
+
+ list_for_each_entry(sw, &gpio_switches, node) {
+ if (old != NULL)
+ kfree(old);
+ flush_scheduled_work();
+ del_timer_sync(&sw->timer);
+
+ if (!sw->failed)
+ free_irq(gpio_to_irq(sw->gpio), sw);
+
+ device_remove_file(&sw->pdev.dev, &dev_attr_state);
+ device_remove_file(&sw->pdev.dev, &dev_attr_type);
+ device_remove_file(&sw->pdev.dev, &dev_attr_direction);
+
+ if (!(sw->flags & OMAP_GPIO_SWITCH_FLAG_OUTPUT))
+ device_remove_file(&sw->pdev.dev, &dev_attr_disable);
+
+ platform_device_unregister(&sw->pdev);
+ if (!sw->failed)
+ gpio_free(sw->gpio);
+ old = sw;
+ }
+ kfree(old);
+}
+
+static void __init report_initial_state(void)
+{
+ struct gpio_switch *sw;
+
+ list_for_each_entry(sw, &gpio_switches, node) {
+ int state = 0;
+
+ if (!sw->failed)
+ state = gpio_get_value(sw->gpio);
+ if (sw->flags & OMAP_GPIO_SWITCH_FLAG_INVERTED)
+ state = !state;
+ if (sw->notify != NULL)
+ sw->notify(sw->notify_data, state);
+ print_sw_state(sw, state);
+ }
+}
+
+static int gpio_sw_remove(struct platform_device *dev)
+{
+ return 0;
+}
+
+static struct platform_driver gpio_sw_driver = {
+ .remove = gpio_sw_remove,
+ .driver = {
+ .name = "gpio-switch",
+ },
+};
+
+void __init omap_register_gpio_switches(const struct omap_gpio_switch *tbl,
+ int count)
+{
+ BUG_ON(board_gpio_sw_table != NULL);
+
+ board_gpio_sw_table = tbl;
+ board_gpio_sw_count = count;
+}
+
+static int __init gpio_sw_init(void)
+{
+ int r;
+
+ printk(KERN_INFO "OMAP GPIO switch handler initializing\n");
+
+ r = platform_driver_register(&gpio_sw_driver);
+ if (r)
+ return r;
+
+ gpio_sw_platform_dev = platform_device_register_simple("gpio-switch",
+ -1, NULL, 0);
+ if (IS_ERR(gpio_sw_platform_dev)) {
+ r = PTR_ERR(gpio_sw_platform_dev);
+ goto err1;
+ }
+
+ r = add_board_switches();
+ if (r < 0)
+ goto err2;
+
+ report_initial_state();
+
+ return 0;
+err2:
+ gpio_sw_cleanup();
+ platform_device_unregister(gpio_sw_platform_dev);
+err1:
+ platform_driver_unregister(&gpio_sw_driver);
+ return r;
+}
+
+static void __exit gpio_sw_exit(void)
+{
+ gpio_sw_cleanup();
+ platform_device_unregister(gpio_sw_platform_dev);
+ platform_driver_unregister(&gpio_sw_driver);
+}
+
+#ifndef MODULE
+late_initcall(gpio_sw_init);
+#else
+module_init(gpio_sw_init);
+#endif
+module_exit(gpio_sw_exit);
+
+MODULE_AUTHOR("Juha Yrj�l� <juha.yrjola@nokia.com>, Paul Mundt <paul.mundt@nokia.com");
+MODULE_DESCRIPTION("GPIO switch driver");
+MODULE_LICENSE("GPL");
--- a/arch/arm/plat-omap/include/plat/gpio-switch.h
+++ b/arch/arm/plat-omap/include/plat/gpio-switch.h
@@ -0,0 +1,60 @@
+/*
+ * GPIO switch definitions
+ *
+ * Copyright (C) 2006 Nokia Corporation
+ *
+ * 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.
+ */
+
+#ifndef __ASM_ARCH_OMAP_GPIO_SWITCH_H
+#define __ASM_ARCH_OMAP_GPIO_SWITCH_H
+
+#include <linux/types.h>
+
+/* Cover:
+ * high -> closed
+ * low -> open
+ * Connection:
+ * high -> connected
+ * low -> disconnected
+ * Activity:
+ * high -> active
+ * low -> inactive
+ *
+ */
+#define OMAP_GPIO_SWITCH_TYPE_COVER 0x0000
+#define OMAP_GPIO_SWITCH_TYPE_CONNECTION 0x0001
+#define OMAP_GPIO_SWITCH_TYPE_ACTIVITY 0x0002
+#define OMAP_GPIO_SWITCH_FLAG_INVERTED 0x0001
+#define OMAP_GPIO_SWITCH_FLAG_OUTPUT 0x0002
+#define OMAP_GPIO_SWITCH_FLAG_OUTPUT_INIT_ACTIVE 0x0004
+
+struct omap_gpio_switch {
+ const char *name;
+ s16 gpio;
+ unsigned flags:4;
+ unsigned type:4;
+
+ /* Time in ms to debounce when transitioning from
+ * inactive state to active state. */
+ u16 debounce_rising;
+ /* Same for transition from active to inactive state. */
+ u16 debounce_falling;
+
+ /* notify board-specific code about state changes */
+ void (* notify)(void *data, int state);
+ void *notify_data;
+ bool failed;
+};
+
+/* Call at init time only */
+#ifdef CONFIG_OMAP_GPIO_SWITCH
+extern void omap_register_gpio_switches(const struct omap_gpio_switch *tbl,
+ int count);
+#else
+#define omap_register_gpio_switches(tbl, count) do { } while (0)
+#endif
+
+#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment