Hi Heiko, On Fri, Oct 21, 2011 at 10:43:47AM +0200, Heiko Stübner wrote: > There exist tilt switches that simply report their tilt-state via some gpios. > > The number and orientation of their axes can vary depending on the switch > used and the build of the device. Also two or more one-axis switches > could be combined to provide multi-dimensional orientation. > > One example of a device using such a switch is the family of Qisda > ebook readers, where the switch provides information about the > landscape / portrait orientation of the device. The example in > Documentation/input/gpio-tilt.txt documents exactly this one-axis device. > Looks very nice, just a few comments below. > Signed-off-by: Heiko Stuebner <heiko@xxxxxxxxx> > --- > Documentation/input/gpio-tilt.txt | 103 ++++++++++++ > drivers/input/misc/Kconfig | 14 ++ > drivers/input/misc/Makefile | 1 + > drivers/input/misc/gpio_tilt_polled.c | 276 +++++++++++++++++++++++++++++++++ > include/linux/gpio_tilt.h | 73 +++++++++ > 5 files changed, 467 insertions(+), 0 deletions(-) > create mode 100644 Documentation/input/gpio-tilt.txt > create mode 100644 drivers/input/misc/gpio_tilt_polled.c > create mode 100644 include/linux/gpio_tilt.h > > diff --git a/Documentation/input/gpio-tilt.txt b/Documentation/input/gpio-tilt.txt > new file mode 100644 > index 0000000..06d60c3 > --- /dev/null > +++ b/Documentation/input/gpio-tilt.txt > @@ -0,0 +1,103 @@ > +Driver for tilt-switches connected via GPIOs > +============================================ > + > +Generic driver to read data from tilt switches connected via gpios. > +Orientation can be provided by one or more than one tilt switches, > +i.e. each tilt switch providing one axis, and the number of axes > +is also not limited. > + > + > +Data structures: > +---------------- > + > +The array of struct gpio in the gpios field is used to list the gpios > +that represent the current tilt state. > + > +The array of struct gpio_tilt_axis describes the axes that are reported > +to the input system. The values set therein are used for the > +input_set_abs_params calls needed to init the axes. > + > +The array of struct gpio_tilt_state maps gpio states to the corresponding > +values to report. The gpio state is represented as a bitfield where the > +bit-index corresponds to the index of the gpio in the struct gpio array. > +In the same manner the values stored in the axes array correspond to > +the elements of the gpio_tilt_axis-array. > + > + > +Example: > +-------- > + > +Example configuration for a single TS1003 tilt switch that rotates around > +one axis in 4 steps and emitts the current tilt via two GPIOs. > + > +static int sg060_tilt_enable(struct device *dev) { > + /* code to enable the sensors */ > +}; > + > +static void sg060_tilt_disable(struct device *dev) { > + /* code to disable the sensors */ > +}; > + > +static struct gpio sg060_tilt_gpios[] = { > + { SG060_TILT_GPIO_SENSOR1, GPIOF_IN, "tilt_sensor1" }, > + { SG060_TILT_GPIO_SENSOR2, GPIOF_IN, "tilt_sensor2" }, > +}; > + > +static struct gpio_tilt_state sg060_tilt_states[] = { > + { > + .gpios = (0 << 1) | (0 << 0), > + .axes = (int[]) { > + 0, > + }, > + }, { > + .gpios = (0 << 1) | (1 << 0), > + .axes = (int[]) { > + 1, /* 90 degrees */ > + }, > + }, { > + .gpios = (1 << 1) | (1 << 0), > + .axes = (int[]) { > + 2, /* 180 degrees */ > + }, > + }, { > + .gpios = (1 << 1) | (0 << 0), > + .axes = (int[]) { > + 3, /* 270 degrees */ > + }, > + }, > +}; > + > +static struct gpio_tilt_axis sg060_tilt_axes[] = { > + { > + .axis = ABS_RY, > + .min = 0, > + .max = 3, > + .fuzz = 0, > + .flat = 0, > + }, > +}; > + > +static struct gpio_tilt_platform_data sg060_tilt_pdata= { > + .gpios = sg060_tilt_gpios, > + .nr_gpios = ARRAY_SIZE(sg060_tilt_gpios), > + > + .axes = sg060_tilt_axes, > + .nr_axes = ARRAY_SIZE(sg060_tilt_axes), > + > + .states = sg060_tilt_states, > + .nr_states = ARRAY_SIZE(sg060_tilt_states), > + > + .debounce_interval = 100, > + > + .poll_interval = 1000, > + .enable = sg060_tilt_enable, > + .disable = sg060_tilt_disable, > +}; > + > +static struct platform_device sg060_device_tilt = { > + .name = "gpio-tilt-polled", > + .id = -1, > + .dev = { > + .platform_data = &sg060_tilt_pdata, > + }, > +}; > diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig > index 22d875f..e53b443 100644 > --- a/drivers/input/misc/Kconfig > +++ b/drivers/input/misc/Kconfig > @@ -179,6 +179,20 @@ config INPUT_APANEL > To compile this driver as a module, choose M here: the module will > be called apanel. > > +config INPUT_GPIO_TILT_POLLED > + tristate "Polled GPIO tilt switch" > + depends on GENERIC_GPIO > + select INPUT_POLLDEV > + help > + This driver implements support for tilt switches connected > + to GPIO pins that are not capable of generating interrupts. > + > + The list of gpios to use and the mapping of their states > + to specific angles is done via platform data. > + > + To compile this driver as a module, choose M here: the > + module will be called gpio_tilt_polled. > + > config INPUT_IXP4XX_BEEPER > tristate "IXP4XX Beeper support" > depends on ARCH_IXP4XX > diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile > index a244fc6..90070c1 100644 > --- a/drivers/input/misc/Makefile > +++ b/drivers/input/misc/Makefile > @@ -23,6 +23,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o > obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o > obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o > obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o > +obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o > obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o > obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o > obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o > diff --git a/drivers/input/misc/gpio_tilt_polled.c b/drivers/input/misc/gpio_tilt_polled.c > new file mode 100644 > index 0000000..15ab935 > --- /dev/null > +++ b/drivers/input/misc/gpio_tilt_polled.c > @@ -0,0 +1,276 @@ > +/* > + * Driver for tilt switches connected via GPIO lines > + * not capable of generating interrupts > + * > + * Copyright (C) 2011 Heiko Stuebner <heiko@xxxxxxxxx> > + * > + * based on: /drivers/input/keyboard/gpio_keys_polled.c > + * > + * Copyright (C) 2007-2010 Gabor Juhos <juhosg@xxxxxxxxxxx> > + * Copyright (C) 2010 Nuno Goncalves <nunojpg@xxxxxxxxx> > + * > + * 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/kernel.h> > +#include <linux/module.h> > +#include <linux/init.h> > +#include <linux/slab.h> > +#include <linux/input.h> > +#include <linux/input-polldev.h> > +#include <linux/ioport.h> > +#include <linux/platform_device.h> > +#include <linux/gpio.h> > +#include <linux/gpio_tilt.h> > + > +#define DRV_NAME "gpio-tilt-polled" > + > +struct gpio_tilt_polled_dev { > + struct input_polled_dev *poll_dev; > + struct device *dev; > + > + struct gpio *gpios; > + int nr_gpios; > + > + struct gpio_tilt_state *states; > + int nr_states; > + > + int *axes; > + int nr_axes; > + > + int last_state; > + > + int threshold; > + int count; > + > + int (*enable)(struct device *dev); > + void (*disable)(struct device *dev); > +}; > + > +static void gpio_tilt_polled_poll(struct input_polled_dev *dev) > +{ > + struct gpio_tilt_polled_dev *bdev = dev->private; Call it tdev or tilt or tilt_dev or something? bdev is not very fitting here. > + struct input_dev *input = dev->input; > + struct gpio_tilt_state *tilt_state = NULL; > + int state, i; > + > + if (bdev->count < bdev->threshold) { > + bdev->count++; > + } else { > + state = 0; > + for (i = 0; i < bdev->nr_gpios; i++) > + state |= (!!gpio_get_value(bdev->gpios[i].gpio) << i); > + > + if (state != bdev->last_state) { > + for (i = 0; i < bdev->nr_states; i++) > + if (bdev->states[i].gpios == state) > + tilt_state = &bdev->states[i]; > + > + if (tilt_state) { > + for (i = 0; i < bdev->nr_axes; i++) > + input_report_abs(input, bdev->axes[i], > + tilt_state->axes[i]); > + > + input_sync(input); > + } > + > + bdev->count = 0; > + bdev->last_state = state; > + } > + } > +} > + > +static void gpio_tilt_polled_open(struct input_polled_dev *dev) > +{ > + struct gpio_tilt_polled_dev *bdev = dev->private; > + > + if (bdev->enable) > + bdev->enable(bdev->dev); > +} > + > +static void gpio_tilt_polled_close(struct input_polled_dev *dev) > +{ > + struct gpio_tilt_polled_dev *bdev = dev->private; > + > + if (bdev->disable) > + bdev->disable(bdev->dev); > +} > + > +static int __devinit gpio_tilt_polled_probe(struct platform_device *pdev) > +{ > + struct gpio_tilt_platform_data *pdata = pdev->dev.platform_data; > + struct device *dev = &pdev->dev; > + struct gpio_tilt_polled_dev *bdev; > + struct input_polled_dev *poll_dev; > + struct input_dev *input; > + int error, i; > + > + if (!pdata || !pdata->poll_interval) > + return -EINVAL; > + > + bdev = kzalloc(sizeof(struct gpio_tilt_polled_dev), GFP_KERNEL); > + if (!bdev) { > + dev_err(dev, "no memory for private data\n"); > + return -ENOMEM; > + } > + > + bdev->gpios = kmemdup(pdata->gpios, > + pdata->nr_gpios * sizeof(struct gpio), > + GFP_KERNEL); > + if (bdev->gpios == NULL) { > + dev_err(&pdev->dev, "Failed to allocate gpio data\n"); > + error = -ENOMEM; > + goto err_free_bdev; > + } > + bdev->nr_gpios = pdata->nr_gpios; Do you actually need to do this? Can't you just store the pointer to the platform data and use it instead? Same goes for the rest of items you are cloning. > + > + error = gpio_request_array(bdev->gpios, bdev->nr_gpios); > + if (error) { > + dev_err(dev, > + "Could not request tilt GPIOs: %d\n", error); > + goto err_free_gpiomem; > + } > + > + bdev->states = kmemdup(pdata->states, > + pdata->nr_states * > + sizeof(struct gpio_tilt_state), > + GFP_KERNEL); > + if (bdev->states == NULL) { > + dev_err(dev, "Failed to allocate state data\n"); > + error = -ENOMEM; > + goto err_free_gpios; > + } > + bdev->nr_states = pdata->nr_states; > + > + bdev->axes = kzalloc(pdata->nr_axes * sizeof(int), GFP_KERNEL); > + if (!bdev->axes) { > + dev_err(dev, "no memory for axes data\n"); > + error = -ENOMEM; > + goto err_free_states; > + } > + > + poll_dev = input_allocate_polled_device(); > + if (!poll_dev) { > + dev_err(dev, "no memory for polled device\n"); > + error = -ENOMEM; > + goto err_free_axes; > + } > + > + poll_dev->private = bdev; > + poll_dev->poll = gpio_tilt_polled_poll; > + poll_dev->poll_interval = pdata->poll_interval; > + poll_dev->open = gpio_tilt_polled_open; > + poll_dev->close = gpio_tilt_polled_close; > + > + input = poll_dev->input; > + > + input->name = pdev->name; > + input->phys = DRV_NAME"/input0"; > + input->dev.parent = &pdev->dev; > + > + input->id.bustype = BUS_HOST; > + input->id.vendor = 0x0001; > + input->id.product = 0x0001; > + input->id.version = 0x0100; > + > + __set_bit(EV_ABS, input->evbit); > + for (i = 0; i < pdata->nr_axes; i++) { > + input_set_abs_params(input, pdata->axes[i].axis, > + pdata->axes[i].min, pdata->axes[i].max, > + pdata->axes[i].fuzz, pdata->axes[i].flat); > + > + bdev->axes[i] = pdata->axes[i].axis; > + } > + bdev->nr_axes = pdata->nr_axes; > + > + bdev->threshold = DIV_ROUND_UP(pdata->debounce_interval, > + pdata->poll_interval); > + > + bdev->poll_dev = poll_dev; > + bdev->dev = dev; > + > + bdev->enable = pdata->enable; > + bdev->disable = pdata->disable; > + > + error = input_register_polled_device(poll_dev); > + if (error) { > + dev_err(dev, "unable to register polled device, err=%d\n", > + error); > + goto err_free_polldev; > + } > + > + platform_set_drvdata(pdev, bdev); > + > + /* report initial state of the buttons */ > + bdev->last_state = -1; > + bdev->count = bdev->threshold; > + gpio_tilt_polled_poll(poll_dev); This should probably be in gpio_tilt_polled_open(). > + > + return 0; > + > +err_free_polldev: > + input_free_polled_device(poll_dev); > +err_free_axes: > + kfree(bdev->axes); > +err_free_states: > + kfree(bdev->states); > +err_free_gpios: > + gpio_free_array(bdev->gpios, bdev->nr_gpios); > +err_free_gpiomem: > + kfree(bdev->gpios); > +err_free_bdev: > + kfree(bdev); > + > + return error; > +} > + > +static int __devexit gpio_tilt_polled_remove(struct platform_device *pdev) > +{ > + struct gpio_tilt_polled_dev *bdev = platform_get_drvdata(pdev); > + > + platform_set_drvdata(pdev, NULL); > + > + input_unregister_polled_device(bdev->poll_dev); > + input_free_polled_device(bdev->poll_dev); > + > + kfree(bdev->axes); > + > + kfree(bdev->states); > + > + gpio_free_array(bdev->gpios, bdev->nr_gpios); > + > + kfree(bdev->gpios); > + > + kfree(bdev); > + > + return 0; > +} > + > +static struct platform_driver gpio_tilt_polled_driver = { > + .probe = gpio_tilt_polled_probe, > + .remove = __devexit_p(gpio_tilt_polled_remove), > + .driver = { > + .name = DRV_NAME, > + .owner = THIS_MODULE, > + }, > +}; > + > +static int __init gpio_tilt_polled_init(void) > +{ > + return platform_driver_register(&gpio_tilt_polled_driver); > +} > + > +static void __exit gpio_tilt_polled_exit(void) > +{ > + platform_driver_unregister(&gpio_tilt_polled_driver); > +} > + > +module_init(gpio_tilt_polled_init); > +module_exit(gpio_tilt_polled_exit); > + > +MODULE_LICENSE("GPL v2"); > +MODULE_AUTHOR("Heiko Stuebner <heiko@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Polled GPIO tilt driver"); > +MODULE_ALIAS("platform:" DRV_NAME); > diff --git a/include/linux/gpio_tilt.h b/include/linux/gpio_tilt.h > new file mode 100644 > index 0000000..809fe93 > --- /dev/null > +++ b/include/linux/gpio_tilt.h > @@ -0,0 +1,73 @@ > +#ifndef _GPIO_TILT_H > +#define _GPIO_TILT_H > + > +/** > + * struct gpio_tilt_axis - Axis used by the tilt switch > + * @axis: Constant describing the axis, e.g. ABS_X > + * @min: minimum value for abs_param > + * @max: maximum value for abs_param > + * @fuzz: fuzz value for abs_param > + * @flat: flat value for abs_param > + */ > +struct gpio_tilt_axis { > + int axis; > + int min; > + int max; > + int fuzz; > + int flat; > +}; > + > +/** > + * struct gpio_tilt_state - state description > + * @gpios: bitfield of gpio target-states for the value > + * @axes: array containing the axes settings for the gpio state > + * The array indizes must correspond to the axes defined > + * in platform_data > + * > + * This structure describes a supported axis settings > + * and the necessary gpio-state which represent it. > + * > + * The n-th bit in the bitfield describes the state of the n-th GPIO > + * from the gpios-array defined in gpio_regulator_config below. > + */ > +struct gpio_tilt_state { > + int gpios; > + int *axes; > +}; > + > +/** > + * struct gpio_tilt_platform_data > + * @gpios: Array containing the gpios determining the tilt state > + * @nr_gpios: Number of gpios > + * @axes: Array of gpio_tilt_axis descriptions > + * @nr_axes: Number of axes > + * @states: Array of gpio_tilt_state entries describing > + * the gpio state for specific tilts > + * @nr_states: Number of states available > + * @debounce_interval: debounce ticks interval in msecs > + * @poll_interval: polling interval in msecs - for polling driver only > + * @enable: callback to enable the tilt switch > + * @disable: callback to disable the tilt switch > + * > + * This structure contains gpio-tilt-switch configuration > + * information that must be passed by platform code to the > + * gpio-tilt input driver. > + */ > +struct gpio_tilt_platform_data { > + struct gpio *gpios; > + int nr_gpios; > + > + struct gpio_tilt_axis *axes; > + int nr_axes; > + > + struct gpio_tilt_state *states; > + int nr_states; > + > + int debounce_interval; > + > + unsigned int poll_interval; > + int (*enable)(struct device *dev); > + void (*disable)(struct device *dev); > +}; > + > +#endif > -- > 1.7.5.4 > Thanks. -- Dmitry -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html