diff mbox

[RFC,14/17] input: RMI4 F30 GPIO/LED control

Message ID 1345241877-16200-15-git-send-email-cheiny@synaptics.com (mailing list archive)
State New, archived
Headers show

Commit Message

Christopher Heiny Aug. 17, 2012, 10:17 p.m. UTC
Signed-off-by: Christopher Heiny <cheiny@synaptics.com>

Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Linus Walleij <linus.walleij@stericsson.com>
Cc: Naveen Kumar Gaddipati <naveen.gaddipati@stericsson.com>
Cc: Joeri de Gram <j.de.gram@gmail.com>

Acked-by: Jean Delvare <khali@linux-fr.org>

---

 drivers/input/rmi4/rmi_f30.c | 1247 ++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 1247 insertions(+), 0 deletions(-)

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Linus Walleij Aug. 27, 2012, 10:58 p.m. UTC | #1
GPIO/LED, nice since I'm a GPIO maintainer I'll take a closer look.

If the bus will start doing a lot of non-input business it should live under
drivers/mfd but I think this is just one exception, right?

On Fri, Aug 17, 2012 at 3:17 PM, Christopher Heiny <cheiny@synaptics.com> wrote:

(...)
> diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c

> +#include <linux/kernel.h>
> +#include <linux/rmi.h>
> +#include <linux/input.h>
> +#include <linux/slab.h>
> +#include "rmi_driver.h"

The non-existance of <linux/gpio.h> and <linux/leds.h> tells us that something
is very wrong.

You should not model these GPIOs and LEDs by a set of obscure
sysfs attributes, instead use the proper kernel subsystems that
already exist for handling this! LEDs and GPIOs already have their
own (standardized) userspace sysfs interfaces.

Reading the code I see that this is what happens here, so please rewrite
this to be a real GPIO+LED driver using struct gpio_chip and
the same for LEDs.

Be inspired by drivers/gpio/* and drivers/leds/*

Yours,
Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Christopher Heiny Sept. 5, 2012, 12:28 a.m. UTC | #2
On 08/27/2012 03:58 PM, Linus Walleij wrote:
> GPIO/LED, nice since I'm a GPIO maintainer I'll take a closer look.
>
> If the bus will start doing a lot of non-input business it should live under
> drivers/mfd but I think this is just one exception, right?
>
> On Fri, Aug 17, 2012 at 3:17 PM, Christopher Heiny <cheiny@synaptics.com> wrote:
>
> (...)
>> diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c
>
>> +#include <linux/kernel.h>
>> +#include <linux/rmi.h>
>> +#include <linux/input.h>
>> +#include <linux/slab.h>
>> +#include "rmi_driver.h"
>
> The non-existance of <linux/gpio.h> and <linux/leds.h> tells us that something
> is very wrong.
>
> You should not model these GPIOs and LEDs by a set of obscure
> sysfs attributes, instead use the proper kernel subsystems that
> already exist for handling this! LEDs and GPIOs already have their
> own (standardized) userspace sysfs interfaces.
>
> Reading the code I see that this is what happens here, so please rewrite
> this to be a real GPIO+LED driver using struct gpio_chip and
> the same for LEDs.
>
> Be inspired by drivers/gpio/* and drivers/leds/*

Roger.  We'll rework this and resubmit at a later date.
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/input/rmi4/rmi_f30.c b/drivers/input/rmi4/rmi_f30.c
new file mode 100644
index 0000000..4bc2932
--- /dev/null
+++ b/drivers/input/rmi4/rmi_f30.c
@@ -0,0 +1,1247 @@ 
+/*
+ * Copyright (c) 2012 Synaptics Incorporated
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#define FUNCTION_DATA rmi_fn_30_data
+#define FNUM 30
+
+#include <linux/kernel.h>
+#include <linux/rmi.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include "rmi_driver.h"
+
+
+#define MAX_LEN 256
+
+/* data specific to fn $30 that needs to be kept around */
+union f30_query {
+	struct {
+		u8 extended_patterns:1;
+		u8 has_mappable_buttons:1;
+		u8 has_led:1;
+		u8 has_gpio:1;
+		u8 has_haptic:1;
+		u8 has_gpio_driver_control:1;
+		u8 gpio_led_count:5;
+	};
+	struct {
+		u8 regs[2];
+		u16 address;
+	};
+};
+
+struct f30_gpio_ctrl_0n {
+	u8 led_sel;
+};
+
+struct f30_gpio_ctrl_0 {
+	struct f30_gpio_ctrl_0n *regs;
+	u16 address;
+	u8 length;
+};
+
+union f30_gpio_ctrl_1 {
+	struct {
+		u8 gpio_debounce:1;
+		u8 reserved:3;
+		u8 halt:1;
+		u8 halted:1;
+		u8 reserved2:2;
+	};
+	struct {
+		u8 regs[1];
+		u16 address;
+	};
+};
+
+struct f30_gpio_ctrl_2n {
+	u8 dir;
+};
+
+struct f30_gpio_ctrl_2 {
+	struct f30_gpio_ctrl_2n *regs;
+	u16 address;
+	u8 length;
+};
+
+struct f30_gpio_ctrl_3n {
+	u8 gpiodata;
+};
+
+struct f30_gpio_ctrl_3 {
+	struct f30_gpio_ctrl_3n *regs;
+	u16 address;
+	u8 length;
+};
+
+struct f30_gpio_ctrl_4n {
+	u8 led_act;
+};
+
+struct f30_gpio_ctrl_4 {
+	struct f30_gpio_ctrl_4n *regs;
+	u16 address;
+	u8 length;
+};
+
+struct f30_gpio_ctrl_5n {
+	u8 ramp_period_a;
+	u8 ramp_period_b;
+};
+
+struct f30_gpio_ctrl_5 {
+	struct f30_gpio_ctrl_5n *regs;
+	u16 address;
+	u8 length;
+};
+
+union f30_gpio_ctrl_6n {
+	struct {
+		u8 reserved:1;
+		u8 SPCTRL:3;
+		u8 STRPD:1;
+		u8 reserved2:1;
+		u8 STRPU:1;
+		u8 reserved3:1;
+	};
+	struct {
+		u8 brightness:4;
+		u8 pattern:4;
+	};
+};
+
+struct f30_gpio_ctrl_6 {
+	union f30_gpio_ctrl_6n *regs;
+	u16 address;
+	u8 length;
+};
+
+struct f30_gpio_ctrl_7n {
+	u8 capacity_btn_nbr:5;
+	u8 valid:1;
+	u8 invert:1;
+	u8 open_drain:1;
+};
+
+struct f30_gpio_ctrl_7 {
+	struct f30_gpio_ctrl_7n *regs;
+	u16 address;
+	u8 length;
+};
+
+struct f30_gpio_ctrl_8n {
+	u8 gpio_ctrl8_0;
+	u8 gpio_ctrl8_1;
+};
+
+struct f30_gpio_ctrl_8 {
+	struct f30_gpio_ctrl_8n *regs;
+	u16 address;
+	u8 length;
+};
+
+union f30_gpio_ctrl_9 {
+	struct {
+		u8 haptic_duration;
+	};
+	struct {
+		u8 regs[1];
+		u16 address;
+	};
+};
+
+struct f30_control {
+	struct f30_gpio_ctrl_0 *reg_0;
+	union f30_gpio_ctrl_1 *reg_1;
+	struct f30_gpio_ctrl_2 *reg_2;
+	struct f30_gpio_ctrl_3 *reg_3;
+	struct f30_gpio_ctrl_4 *reg_4;
+	struct f30_gpio_ctrl_5 *reg_5;
+	struct f30_gpio_ctrl_6 *reg_6;
+	struct f30_gpio_ctrl_7 *reg_7;
+	struct f30_gpio_ctrl_8 *reg_8;
+	union f30_gpio_ctrl_9 *reg_9;
+};
+
+struct f30_data_0n {
+	u8 gpi_led_data:1;
+};
+
+struct f30_data_0 {
+	struct f30_data_0n *regs;
+	u16 address;
+	u8 length;
+};
+
+struct f30_data {
+	struct f30_data_0 *datareg_0;
+	u16 address;
+	u8 length;
+};
+
+struct rmi_fn_30_data {
+	union f30_query query;
+	struct f30_data data;
+	struct f30_control control;
+	unsigned char gpioled_count;
+	unsigned char gpioled_bitmask_size;
+	unsigned char gpioled_byte_size;
+	unsigned char *button_data_buffer;
+	unsigned char button_bitmask_size;
+	unsigned short *gpioled_map;
+	char input_name[MAX_LEN];
+	char input_phys[MAX_LEN];
+	struct input_dev *input;
+	struct mutex control_mutex;
+	struct mutex data_mutex;
+};
+
+static int rmi_f30_alloc_memory(struct rmi_function_container *fc);
+
+static void rmi_f30_free_memory(struct rmi_function_container *fc);
+
+static int rmi_f30_initialize(struct rmi_function_container *fc);
+
+static int rmi_f30_register_device(struct rmi_function_container *fc);
+
+static int rmi_f30_config(struct rmi_function_container *fc);
+
+static int rmi_f30_create_sysfs(struct rmi_function_container *fc);
+
+
+/* Query sysfs files */
+
+show_union_struct_prototype(extended_patterns)
+show_union_struct_prototype(has_mappable_buttons)
+show_union_struct_prototype(has_led)
+show_union_struct_prototype(has_gpio)
+show_union_struct_prototype(has_haptic)
+show_union_struct_prototype(has_gpio_driver_control)
+show_union_struct_prototype(gpio_led_count)
+
+static struct attribute *attrs1[] = {
+	attrify(extended_patterns),
+	attrify(has_mappable_buttons),
+	attrify(has_led),
+	attrify(has_gpio),
+	attrify(has_haptic),
+	attrify(has_gpio_driver_control),
+	attrify(gpio_led_count),
+	NULL
+};
+
+static struct attribute_group attrs_query = GROUP(attrs1);
+
+/* Control sysfs files */
+
+show_store_union_struct_prototype(led_sel)
+show_store_union_struct_prototype(gpio_debounce)
+show_store_union_struct_prototype(halt)
+show_store_union_struct_prototype(halted)
+show_store_union_struct_prototype(dir)
+show_store_union_struct_prototype(gpiodata)
+show_store_union_struct_prototype(led_act)
+show_store_union_struct_prototype(ramp_period_a)
+show_store_union_struct_prototype(ramp_period_b)
+show_store_union_struct_prototype(SPCTRL)
+show_store_union_struct_prototype(STRPD)
+show_store_union_struct_prototype(STRPU)
+show_store_union_struct_prototype(brightness)
+show_store_union_struct_prototype(pattern)
+
+show_store_union_struct_prototype(capacity_btn_nbr)
+show_store_union_struct_prototype(valid)
+show_store_union_struct_prototype(invert)
+show_store_union_struct_prototype(open_drain)
+show_store_union_struct_prototype(gpio_ctrl8_0)
+show_store_union_struct_prototype(gpio_ctrl8_1)
+show_store_union_struct_prototype(haptic_duration)
+
+/* Data sysfs files */
+show_store_union_struct_prototype(gpi_led_data)
+
+static struct attribute *attrs_ctrl_reg_0[] = {
+	attrify(led_sel),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_1[] = {
+	attrify(gpio_debounce),
+	attrify(halt),
+	attrify(halted),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_2[] = {
+	attrify(dir),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_3[] = {
+	attrify(gpiodata),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_4[] = {
+	attrify(led_act),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_5[] = {
+	attrify(ramp_period_a),
+	attrify(ramp_period_b),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_6_gpio[] = {
+	attrify(SPCTRL),
+	attrify(STRPD),
+	attrify(STRPU),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_6_led[] = {
+	attrify(brightness),
+	attrify(pattern),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_7[] = {
+	attrify(capacity_btn_nbr),
+	attrify(valid),
+	attrify(invert),
+	attrify(open_drain),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_8[] = {
+	attrify(gpio_ctrl8_0),
+	attrify(gpio_ctrl8_1),
+	NULL
+};
+
+static struct attribute *attrs_ctrl_reg_9[] = {
+	attrify(haptic_duration),
+	NULL
+};
+
+static struct attribute_group attrs_ctrl_regs[] = {
+	GROUP(attrs_ctrl_reg_0),
+	GROUP(attrs_ctrl_reg_1),
+	GROUP(attrs_ctrl_reg_2),
+	GROUP(attrs_ctrl_reg_3),
+	GROUP(attrs_ctrl_reg_4),
+	GROUP(attrs_ctrl_reg_5),
+	GROUP(attrs_ctrl_reg_6_gpio),
+	GROUP(attrs_ctrl_reg_6_led),
+	GROUP(attrs_ctrl_reg_7),
+	GROUP(attrs_ctrl_reg_8),
+	GROUP(attrs_ctrl_reg_9),
+};
+
+bool f30_attrs_regs_exist[ARRAY_SIZE(attrs_ctrl_regs)];
+
+static struct attribute *attrs_gpileddata[] = {
+	attrify(gpi_led_data),
+	NULL
+};
+
+static struct attribute_group attrs_data = GROUP(attrs_gpileddata);
+
+int rmi_f30_read_control_parameters(struct rmi_device *rmi_dev,
+	struct rmi_fn_30_data *f30)
+{
+	int retval = 0;
+	struct f30_control *control = &f30->control;
+
+	retval = rmi_read_block(rmi_dev, control->reg_0->address,
+			(u8 *)control->reg_0->regs,
+			control->reg_0->length);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg0 to 0x%x\n",
+			__func__, control->reg_0->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_1->address,
+			(u8 *)control->reg_1->regs,
+			sizeof(control->reg_1->regs));
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg1 to 0x%x\n",
+			 __func__, control->reg_1->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_2->address,
+			(u8 *)control->reg_2->regs, control->reg_2->length);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg_2 to 0x%x\n",
+			 __func__, control->reg_2->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_3->address,
+			(u8 *)control->reg_3->regs, control->reg_3->length);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg_3 to 0x%x\n",
+			 __func__, control->reg_3->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_4->address,
+			(u8 *)control->reg_4->regs, control->reg_4->length);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg4 to 0x%x\n",
+			 __func__, control->reg_4->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_5->address,
+			(u8 *)control->reg_5->regs,
+			control->reg_5->length);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg5 to 0x%x\n", __func__,
+			control->reg_5->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_6->address,
+			(u8 *)control->reg_6->regs,
+			control->reg_6->length);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg6 to 0x%x\n", __func__,
+		control->reg_6->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_7->address,
+		(u8 *)control->reg_7->regs,
+		control->reg_7->length);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg7 to 0x%x\n", __func__,
+		control->reg_7->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_8->address,
+		(u8 *)control->reg_8->regs,
+		control->reg_8->length);
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg8 to 0x%x\n", __func__,
+		control->reg_8->address);
+		return retval;
+	}
+
+	retval = rmi_read_block(rmi_dev, control->reg_9->address,
+		(u8 *)control->reg_9->regs,
+		sizeof(control->reg_9->regs));
+	if (retval < 0) {
+		dev_err(&rmi_dev->dev,
+			"%s : Could not read control reg9 to 0x%x\n", __func__,
+		control->reg_9->address);
+		return retval;
+	}
+	return 0;
+}
+
+static int rmi_f30_init(struct rmi_function_container *fc)
+{
+	int rc;
+	struct rmi_fn_30_data *f30 = fc->data;
+
+	rc = rmi_f30_alloc_memory(fc);
+	if (rc < 0)
+		goto error_exit;
+
+	rc = rmi_f30_initialize(fc);
+	if (rc < 0)
+		goto error_exit;
+
+	rc = rmi_f30_register_device(fc);
+	if (rc < 0)
+		goto error_exit;
+
+	rc = rmi_f30_create_sysfs(fc);
+	if (rc < 0)
+		goto error_uregister_exit;
+	return 0;
+
+error_uregister_exit:
+	input_unregister_device(f30->input);
+
+error_exit:
+	rmi_f30_free_memory(fc);
+
+	return rc;
+
+}
+
+static inline int rmi_f30_alloc_memory(struct rmi_function_container *fc)
+{
+	struct rmi_fn_30_data *f30;
+	int retval;
+
+	f30 = kzalloc(sizeof(struct rmi_fn_30_data), GFP_KERNEL);
+	if (!f30) {
+		dev_err(&fc->dev, "Failed to allocate rmi_fn_30_data.\n");
+		return -ENOMEM;
+	}
+	fc->data = f30;
+
+	retval = rmi_read_block(fc->rmi_dev,
+						fc->fd.query_base_addr,
+						f30->query.regs,
+					ARRAY_SIZE(f30->query.regs));
+
+	if (retval < 0) {
+		dev_err(&fc->dev, "Failed to read query register.\n");
+		return retval;
+	}
+
+	f30->gpioled_count = f30->query.gpio_led_count;
+	f30->button_bitmask_size = sizeof(u8)*(f30->gpioled_count + 7) / 8;
+	f30->button_data_buffer =
+	    kcalloc(f30->button_bitmask_size,
+		    sizeof(unsigned char), GFP_KERNEL);
+	if (!f30->button_data_buffer) {
+		dev_err(&fc->dev, "Failed to allocate button data buffer.\n");
+		return -ENOMEM;
+	}
+
+	f30->gpioled_map = kcalloc(f30->gpioled_count,
+				sizeof(unsigned short), GFP_KERNEL);
+	if (!f30->gpioled_map) {
+		dev_err(&fc->dev, "Failed to allocate button map.\n");
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static inline void rmi_f30_free_memory(struct rmi_function_container *fc)
+{
+	struct rmi_fn_30_data *f30 = fc->data;
+	u8 reg_num = 0;
+	sysfs_remove_group(&fc->dev.kobj, &attrs_query);
+	sysfs_remove_group(&fc->dev.kobj, &attrs_data);
+	for (reg_num = 0; reg_num < ARRAY_SIZE(attrs_ctrl_regs); reg_num++)
+		sysfs_remove_group(&fc->dev.kobj, &attrs_ctrl_regs[reg_num]);
+	if (f30) {
+		if (f30->control.reg_0)
+			kfree(f30->control.reg_0->regs);
+		kfree(f30->control.reg_0);
+
+		if (f30->control.reg_1)
+			kfree(f30->control.reg_1->regs);
+		kfree(f30->control.reg_1);
+
+		if (f30->control.reg_2)
+			kfree(f30->control.reg_2->regs);
+		kfree(f30->control.reg_2);
+
+		if (f30->control.reg_3)
+			kfree(f30->control.reg_3->regs);
+		kfree(f30->control.reg_3);
+
+		if (f30->control.reg_4)
+			kfree(f30->control.reg_4->regs);
+		kfree(f30->control.reg_4);
+
+		if (f30->control.reg_5)
+			kfree(f30->control.reg_5->regs);
+		kfree(f30->control.reg_5);
+
+		if (f30->control.reg_6)
+			kfree(f30->control.reg_6->regs);
+		kfree(f30->control.reg_6);
+
+		if (f30->control.reg_7)
+			kfree(f30->control.reg_7->regs);
+		kfree(f30->control.reg_7);
+
+		if (f30->control.reg_8)
+			kfree(f30->control.reg_8->regs);
+		kfree(f30->control.reg_8);
+
+		if (f30->control.reg_9)
+			kfree(f30->control.reg_9->regs);
+		kfree(f30->control.reg_9);
+
+		if (!f30->data.datareg_0)
+			kfree(f30->data.datareg_0->regs);
+		kfree(f30->data.datareg_0);
+
+		kfree(f30->button_data_buffer);
+		kfree(f30->gpioled_map);
+		kfree(f30);
+		fc->data = NULL;
+	}
+}
+
+int rmi_f30_attention(struct rmi_function_container *fc, u8 *irq_bits)
+{
+	struct rmi_device *rmi_dev = fc->rmi_dev;
+	int data_base_addr = fc->fd.data_base_addr;
+	struct rmi_fn_30_data *f30 = fc->data;
+	int error;
+	int gpiled;
+	bool gpiled_status = false;
+	int status = 0;
+
+	/* Read the button data. */
+
+	error = rmi_read_block(rmi_dev, data_base_addr,
+			f30->button_data_buffer,
+			f30->button_bitmask_size);
+	if (error < 0) {
+		dev_err(&fc->dev,
+			"%s: Failed to read button data registers.\n",
+			__func__);
+		return error;
+	}
+
+	/* Read the gpi led data. */
+	f30->data.address = fc->fd.data_base_addr;
+	error = rmi_read_block(fc->rmi_dev, f30->data.address,
+		(u8 *)&f30->data, f30->gpioled_count);
+
+	if (error < 0) {
+		dev_err(&fc->dev, "%s: Failed to read f30 data registers.\n",
+			__func__);
+		return error;
+	}
+	/* Generate events for buttons that change state. */
+	for (gpiled = 0; gpiled < f30->gpioled_count; gpiled++) {
+		status = f30->data.datareg_0->regs[gpiled].gpi_led_data;
+		dev_warn(&fc->dev,
+			"rmi_f30 attention gpiled=%d data status=%d\n",
+			gpiled,
+			f30->data.datareg_0->regs[gpiled].gpi_led_data);
+		/* check if gpio */
+		if (!(f30->control.reg_0->regs[gpiled].led_sel)) {
+			if (f30->control.reg_2->regs[gpiled].dir == 0) {
+				gpiled_status = status != 0;
+
+		/* if the gpiled data state changed from the
+		* last time report it and store the new state */
+		/* Generate an event here. */
+			dev_warn(&fc->dev,
+			"rmi_f30 attention call input_report_key\n");
+			input_report_key(f30->input,
+				f30->data.datareg_0->regs[gpiled].gpi_led_data,
+				gpiled_status);
+			}
+		}
+	}
+	input_sync(f30->input); /* sync after groups of events */
+	return 0;
+}
+
+static int rmi_f30_register_device(struct rmi_function_container *fc)
+{
+	struct rmi_device *rmi_dev = fc->rmi_dev;
+	struct input_dev *input_dev;
+	struct rmi_fn_30_data *f30 = fc->data;
+	int i;
+	int rc;
+
+	input_dev = input_allocate_device();
+	if (!input_dev) {
+		dev_err(&fc->dev, "Failed to allocate input device.\n");
+		return -ENOMEM;
+	}
+
+	f30->input = input_dev;
+	snprintf(f30->input_name, MAX_LEN, "%sfn%02x", dev_name(&rmi_dev->dev),
+		fc->fd.function_number);
+	input_dev->name = f30->input_name;
+	snprintf(f30->input_phys, MAX_LEN, "%s/input0", input_dev->name);
+	input_dev->phys = f30->input_phys;
+	input_dev->dev.parent = &rmi_dev->dev;
+	input_set_drvdata(input_dev, f30);
+
+	/* Set up any input events. */
+	set_bit(EV_SYN, input_dev->evbit);
+	set_bit(EV_KEY, input_dev->evbit);
+	input_dev->keycode = f30->gpioled_map;
+	input_dev->keycodesize = 1;
+	input_dev->keycodemax = f30->gpioled_count;
+	/* set bits for each qpio led pin... */
+	for (i = 0; i < f30->gpioled_count; i++) {
+		set_bit(f30->gpioled_map[i], input_dev->keybit);
+		input_set_capability(input_dev, EV_KEY, f30->gpioled_map[i]);
+	}
+
+	rc = input_register_device(input_dev);
+	if (rc < 0) {
+		dev_err(&fc->dev, "Failed to register input device.\n");
+		goto error_free_device;
+	}
+	return 0;
+
+error_free_device:
+	input_free_device(input_dev);
+
+	return rc;
+}
+
+
+static int rmi_f30_config(struct rmi_function_container *fc)
+{
+	struct rmi_fn_30_data *data = fc->data;
+	int gpio_led_cnt = data->query.gpio_led_count;
+	int bytecnt = gpio_led_cnt / 7 + 1;
+	int regs_size = 0;
+	int rc;
+	/* repeated register functions */
+
+	/* Write Control Register values back to device */
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_0->address,
+				(u8 *)data->control.reg_0,
+				bytecnt * sizeof(struct f30_gpio_ctrl_0n));
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 0 to 0x%x\n",
+				__func__, rc, data->control.reg_0->address);
+		return rc;
+	}
+
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_1->address,
+			(u8 *) data->control.reg_1->regs,
+			sizeof(union f30_gpio_ctrl_1));
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 1 to 0x%x\n",
+				__func__, rc, data->control.reg_1->address);
+		return rc;
+	}
+
+	regs_size = data->control.reg_2->length;
+
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_2->address,
+			(u8 *) data->control.reg_2->regs,
+			regs_size);
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 2 to 0x%x\n",
+				__func__, rc, data->control.reg_2->address);
+		return rc;
+	}
+
+	regs_size = data->control.reg_3->length;
+
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_3->address,
+			(u8 *) data->control.reg_3->regs,
+			regs_size);
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 3 to 0x%x\n",
+				__func__, rc, data->control.reg_3->address);
+		return rc;
+	}
+
+	regs_size = data->control.reg_4->length;
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_4->address,
+			(u8 *) data->control.reg_4->regs,
+			regs_size);
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 4 to 0x%x\n",
+				__func__, rc, data->control.reg_4->address);
+		return rc;
+	}
+
+	regs_size = data->control.reg_5->length;
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_5->address,
+			(u8 *) data->control.reg_5->regs,
+			regs_size);
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 5 to 0x%x\n",
+				__func__, rc, data->control.reg_5->address);
+		return rc;
+	}
+
+	regs_size = data->control.reg_6->length;
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_6->address,
+			(u8 *) data->control.reg_6->regs,
+			regs_size);
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 6 to 0x%x\n",
+				__func__, rc, data->control.reg_6->address);
+		return rc;
+	}
+
+	regs_size = data->control.reg_7->length;
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_7->address,
+			(u8 *) data->control.reg_7->regs,
+			regs_size);
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 7 to 0x%x\n",
+			__func__, rc, data->control.reg_7->address);
+		return rc;
+	}
+
+	regs_size = data->control.reg_8->length;
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_8->address,
+			(u8 *) data->control.reg_8->regs, regs_size);
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 9 to 0x%x\n",
+			__func__, rc, data->control.reg_8->address);
+		return rc;
+	}
+
+	rc = rmi_write_block(fc->rmi_dev, data->control.reg_9->address,
+			(u8 *) data->control.reg_9->regs,
+			sizeof(union f30_gpio_ctrl_9));
+	if (rc < 0) {
+		dev_err(&fc->dev, "%s error %d: Could not write control 9 to 0x%x\n",
+				__func__, rc, data->control.reg_9->address);
+		return rc;
+	}
+
+	return 0;
+}
+
+static inline int rmi_f30_initialize(struct rmi_function_container *fc)
+{
+	struct rmi_device *rmi_dev = fc->rmi_dev;
+	struct rmi_device_platform_data *pdata;
+	struct rmi_fn_30_data *instance_data = fc->data;
+
+	int retval = 0;
+	u16 next_loc;
+	int  gpio_led_cnt = 0;
+	int regs_size = 0;
+	u8 reg_num = 0;
+	int reg_flg;
+	int hasgpio, hasled, hasmbtn, hashaptic;
+	struct f30_control *control = &instance_data->control;
+
+	/* Read F30 Query Data */
+	instance_data->query.address = fc->fd.query_base_addr;
+	retval = rmi_read_block(fc->rmi_dev, instance_data->query.address,
+		(u8 *)&instance_data->query, sizeof(instance_data->query));
+	if (retval < 0) {
+		dev_err(&fc->dev,
+		"Could not read query registers from 0x%04x\n",
+		instance_data->query.address);
+		return retval;
+	}
+
+	/* initialize gpioled_map data */
+	hasgpio = instance_data->query.has_gpio;
+	hasled = instance_data->query.has_led;
+	hasmbtn = instance_data->query.has_mappable_buttons;
+	hashaptic = instance_data->query.has_haptic ;
+	gpio_led_cnt = instance_data->query.gpio_led_count;
+
+	pdata = to_rmi_platform_data(rmi_dev);
+	if (pdata) {
+		if (!pdata->gpioled_map) {
+			dev_warn(&fc->dev,
+			"%s - gpioled_map is NULL", __func__);
+		} else if (pdata->gpioled_map->ngpioleds != gpio_led_cnt) {
+			dev_warn(&fc->dev,
+				"Platformdata gpioled map size (%d) != number "
+				"of buttons on device (%d) - ignored.\n",
+				pdata->gpioled_map->ngpioleds,
+				gpio_led_cnt);
+		} else if (!pdata->gpioled_map->map) {
+			dev_warn(&fc->dev,
+				 "Platformdata button map is missing!\n");
+		} else {
+			int i;
+			for (i = 0; i < pdata->gpioled_map->ngpioleds; i++)
+				instance_data->gpioled_map[i] =
+					pdata->gpioled_map->map[i];
+		}
+	}
+
+	/* Initialize Control Data */
+
+	next_loc = fc->fd.control_base_addr;
+
+	/* calculate reg size */
+
+	instance_data->gpioled_bitmask_size = sizeof(u8)*(gpio_led_cnt + 7) / 8;
+	instance_data->gpioled_byte_size = sizeof(u8)*gpio_led_cnt;
+
+	/* reg_0 */
+	control->reg_0 =
+			kzalloc(sizeof(struct f30_gpio_ctrl_0), GFP_KERNEL);
+	if (!control->reg_0) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+
+	if (hasgpio && hasled)
+		reg_flg = 1;
+
+	f30_attrs_regs_exist[reg_num] = true;
+	regs_size = max(sizeof(struct f30_gpio_ctrl_0n) * reg_flg *
+					instance_data->gpioled_bitmask_size, 1);
+	control->reg_0->regs =
+			kzalloc(regs_size, GFP_KERNEL);
+
+	if (!control->reg_0->regs) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	control->reg_0->address = next_loc;
+	control->reg_0->length = regs_size;
+	next_loc += regs_size;
+	reg_num++;
+
+	/* reg_1 */
+	control->reg_1 =
+			kzalloc(sizeof(union f30_gpio_ctrl_1), GFP_KERNEL);
+	if (!control->reg_1) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	f30_attrs_regs_exist[reg_num] = true;
+	reg_num++;
+	instance_data->control.reg_1->address = next_loc;
+	next_loc += regs_size;
+
+	/* reg_2 */
+	instance_data->control.reg_2 =
+			kzalloc(sizeof(struct f30_gpio_ctrl_2), GFP_KERNEL);
+	if (!control->reg_2) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+
+	reg_flg = hasgpio;
+	f30_attrs_regs_exist[reg_num] = true;
+	regs_size = max(sizeof(struct f30_gpio_ctrl_2n)*reg_flg*
+					instance_data->gpioled_bitmask_size, 1);
+	control->reg_2->regs =
+			kzalloc(regs_size, GFP_KERNEL);
+
+	if (!control->reg_2->regs) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	control->reg_2->address = next_loc;
+	control->reg_2->length = regs_size;
+	next_loc += regs_size;
+	reg_num++;
+
+	/* reg_3 */
+	instance_data->control.reg_3 =
+			kzalloc(sizeof(struct f30_gpio_ctrl_3), GFP_KERNEL);
+	if (!control->reg_3) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+
+	reg_flg = hasgpio;
+	f30_attrs_regs_exist[reg_num] = true;
+	regs_size = max(sizeof(struct f30_gpio_ctrl_3n) * reg_flg *
+					instance_data->gpioled_bitmask_size, 1);
+	control->reg_3->regs =
+			kzalloc(regs_size, GFP_KERNEL);
+
+	if (!control->reg_3->regs) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	control->reg_3->address = next_loc;
+	control->reg_3->length = regs_size;
+	next_loc += regs_size;
+	reg_num++;
+
+	/* reg_4 */
+	control->reg_4 =
+			kzalloc(sizeof(struct f30_gpio_ctrl_4), GFP_KERNEL);
+	if (!control->reg_4) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+
+	reg_flg = hasled;
+	f30_attrs_regs_exist[reg_num] = true;
+	regs_size = max(sizeof(struct f30_gpio_ctrl_4n)*reg_flg*
+					instance_data->gpioled_bitmask_size,
+					sizeof(struct f30_gpio_ctrl_4n));
+	control->reg_4->regs =
+			kzalloc(regs_size, GFP_KERNEL);
+	if (!control->reg_4->regs) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	control->reg_4->address = next_loc;
+	control->reg_4->length = regs_size;
+	next_loc += regs_size;
+	reg_num++;
+
+	/* reg_5 */
+	control->reg_5 =
+			kzalloc(sizeof(struct f30_gpio_ctrl_5), GFP_KERNEL);
+	if (!control->reg_5) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+
+	reg_flg = hasled;
+	f30_attrs_regs_exist[reg_num] = true;
+	regs_size = max(6 * reg_flg, 2);
+	control->reg_5->regs =
+			kzalloc(regs_size, GFP_KERNEL);
+	if (!control->reg_5->regs) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	control->reg_5->address = next_loc;
+	control->reg_5->length = regs_size;
+	next_loc += regs_size;
+	reg_num++;
+
+	/* reg_6 */
+	control->reg_6 =
+			kzalloc(sizeof(struct f30_gpio_ctrl_6), GFP_KERNEL);
+	if (!control->reg_6) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	reg_flg = hasled || (!hasled
+		&& instance_data->query.has_gpio_driver_control);
+
+	regs_size = max(sizeof(union f30_gpio_ctrl_6n)*reg_flg*gpio_led_cnt,
+					sizeof(union f30_gpio_ctrl_6n));
+	if (!hasled
+		&& instance_data->query.has_gpio_driver_control)
+		f30_attrs_regs_exist[reg_num] = true;
+
+	reg_num++;
+	if (hasled)
+		f30_attrs_regs_exist[reg_num] = true;
+
+	reg_num++;
+
+	control->reg_6->regs =
+			kzalloc(regs_size, GFP_KERNEL);
+	if (!control->reg_6->regs) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	control->reg_6->address = next_loc;
+	control->reg_6->length = regs_size;
+	next_loc += regs_size;
+
+	/* reg_7 */
+	reg_flg = hasmbtn;
+	control->reg_7 =
+			kzalloc(sizeof(struct f30_gpio_ctrl_7), GFP_KERNEL);
+	if (!control->reg_7) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	if (hasmbtn)
+		regs_size = sizeof(struct f30_gpio_ctrl_7n)*gpio_led_cnt;
+	else
+		regs_size = sizeof(struct f30_gpio_ctrl_7n);
+
+	regs_size = max(sizeof(struct f30_gpio_ctrl_7n)*reg_flg*
+					gpio_led_cnt,
+					sizeof(struct f30_gpio_ctrl_7n));
+	f30_attrs_regs_exist[reg_num] = true;
+	control->reg_7->regs =
+			kzalloc(regs_size, GFP_KERNEL);
+	if (!control->reg_7->regs) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	control->reg_7->address = next_loc;
+	control->reg_7->length = regs_size;
+	next_loc += regs_size;
+	reg_num++;
+
+	/* reg_8 */
+	control->reg_8 =
+			kzalloc(sizeof(struct f30_gpio_ctrl_8), GFP_KERNEL);
+	if (!control->reg_8) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+
+	regs_size = max(sizeof(struct f30_gpio_ctrl_8n)*hashaptic*
+					gpio_led_cnt,
+					sizeof(struct f30_gpio_ctrl_8n));
+	f30_attrs_regs_exist[reg_num] = true;
+	control->reg_8->regs =
+			kzalloc(regs_size, GFP_KERNEL);
+	if (!control->reg_8->regs) {
+		dev_err(&fc->dev, "Failed to allocate control registers.");
+		return -ENOMEM;
+	}
+	control->reg_8->address = next_loc;
+	control->reg_8->length = regs_size;
+	next_loc += regs_size;
+	reg_num++;
+
+	/* reg_9 */
+	control->reg_9 =
+			kzalloc(sizeof(union f30_gpio_ctrl_9), GFP_KERNEL);
+	if (!control->reg_9) {
+		dev_err(&fc->dev, "Failed to allocate control register.");
+		return -ENOMEM;
+	}
+	if (instance_data->query.has_haptic)
+		f30_attrs_regs_exist[reg_num] = true;
+	control->reg_9->address = next_loc;
+	next_loc += sizeof(control->reg_9->regs);
+
+	/* data reg_0 */
+	instance_data->data.datareg_0 =
+			kzalloc(sizeof(struct f30_data_0), GFP_KERNEL);
+	if (!instance_data->data.datareg_0) {
+		dev_err(&fc->dev, "Failed to allocate control register.");
+		return -ENOMEM;
+	}
+
+	regs_size = sizeof(struct f30_data_0n)*
+				instance_data->gpioled_byte_size;
+	instance_data->data.datareg_0->address = fc->fd.data_base_addr;
+	next_loc += sizeof(instance_data->data.datareg_0->regs);
+
+	retval = rmi_f30_read_control_parameters(rmi_dev, instance_data);
+	if (retval < 0) {
+		dev_err(&fc->dev,
+			"Failed to initialize F19 control params.\n");
+		return retval;
+	}
+
+	mutex_init(&instance_data->control_mutex);
+	mutex_init(&instance_data->data_mutex);
+	return 0;
+}
+
+static int rmi_f30_create_sysfs(struct rmi_function_container *fc)
+{
+	u8 reg_num;
+
+	dev_dbg(&fc->dev, "Creating sysfs files.");
+
+	/* Set up sysfs device attributes. */
+	if (sysfs_create_group(&fc->dev.kobj, &attrs_query) < 0) {
+		dev_err(&fc->dev, "Failed to create query sysfs files.");
+		return -ENODEV;
+	}
+	if (sysfs_create_group(&fc->dev.kobj, &attrs_data) < 0) {
+		dev_err(&fc->dev, "Failed to create data sysfs files.");
+		return -ENODEV;
+	}
+
+	for (reg_num = 0; reg_num < ARRAY_SIZE(attrs_ctrl_regs);
+		reg_num++) {
+		if (f30_attrs_regs_exist[reg_num]) {
+			if (sysfs_create_group(&fc->dev.kobj,
+					&attrs_ctrl_regs[reg_num]) < 0) {
+				dev_err(&fc->dev,
+					"Failed to create "
+					"sysfs file group for reg"
+					"group %d.",
+					reg_num);
+				return -ENODEV;
+			}
+
+		}
+	}
+
+	return 0;
+}
+
+static void rmi_f30_remove(struct rmi_function_container *fc)
+{
+	dev_info(&fc->dev, "Removing F30.");
+	rmi_f30_free_memory(fc);
+}
+
+static struct rmi_function_handler function_handler = {
+	.func = 0x30,
+	.init = rmi_f30_init,
+	.config = rmi_f30_config,
+	.attention = rmi_f30_attention,
+	.remove = rmi_f30_remove
+};
+
+static int __init rmi_f30_module_init(void)
+{
+	int error;
+
+	error = rmi_register_function_driver(&function_handler);
+	if (error < 0) {
+		pr_err("%s: register failed!\n", __func__);
+		return error;
+	}
+	return 0;
+}
+
+static void rmi_f30_module_exit(void)
+{
+	rmi_unregister_function_driver(&function_handler);
+}
+
+/* sysfs functions */
+/* Query */
+simple_show_union_struct_unsigned(query, extended_patterns)
+simple_show_union_struct_unsigned(query, has_mappable_buttons)
+simple_show_union_struct_unsigned(query, has_led)
+simple_show_union_struct_unsigned(query, has_gpio)
+simple_show_union_struct_unsigned(query, has_haptic)
+simple_show_union_struct_unsigned(query, has_gpio_driver_control)
+simple_show_union_struct_unsigned(query, gpio_led_count)
+
+/* Control */
+show_store_union_struct_unsigned(control, reg_1, gpio_debounce)
+show_store_union_struct_unsigned(control, reg_1, halt)
+show_store_union_struct_unsigned(control, reg_1, halted)
+show_store_union_struct_unsigned(control, reg_9, haptic_duration)
+
+/* repeated register functions */
+show_store_repeated_union_struct_unsigned(control, reg_0, led_sel)
+show_store_repeated_union_struct_unsigned(control, reg_2, dir)
+show_store_repeated_union_struct_unsigned(control, reg_3, gpiodata)
+show_store_repeated_union_struct_unsigned(control, reg_4, led_act)
+show_store_repeated_union_struct_unsigned(control, reg_5, ramp_period_a)
+show_store_repeated_union_struct_unsigned(control, reg_5, ramp_period_b)
+show_store_repeated_union_struct_unsigned(control, reg_6, SPCTRL)
+show_store_repeated_union_struct_unsigned(control, reg_6, STRPD)
+show_store_repeated_union_struct_unsigned(control, reg_6, STRPU)
+show_store_repeated_union_struct_unsigned(control, reg_6, brightness)
+show_store_repeated_union_struct_unsigned(control, reg_6, pattern)
+show_store_repeated_union_struct_unsigned(control, reg_7, capacity_btn_nbr)
+show_store_repeated_union_struct_unsigned(control, reg_7, valid)
+show_store_repeated_union_struct_unsigned(control, reg_7, invert)
+show_store_repeated_union_struct_unsigned(control, reg_7, open_drain)
+show_store_repeated_union_struct_unsigned(control, reg_8, gpio_ctrl8_0)
+show_store_repeated_union_struct_unsigned(control, reg_8, gpio_ctrl8_1)
+
+/* Data */
+show_store_repeated_union_struct_unsigned(data, datareg_0, gpi_led_data)
+
+module_init(rmi_f30_module_init);
+module_exit(rmi_f30_module_exit);
+
+MODULE_AUTHOR("Allie Xiong <axiong@Synaptics.com>");
+MODULE_DESCRIPTION("RMI f30 module");
+MODULE_LICENSE("GPL");