summaryrefslogtreecommitdiff
path: root/drivers/input/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/misc')
-rw-r--r--drivers/input/misc/Kconfig48
-rw-r--r--drivers/input/misc/Makefile4
-rw-r--r--drivers/input/misc/ab5500-accdet.c284
-rw-r--r--drivers/input/misc/ab8500-accdet.c451
-rw-r--r--drivers/input/misc/ab8500-ponkey.c213
-rw-r--r--drivers/input/misc/abx500-accdet.c1011
-rw-r--r--drivers/input/misc/lps001wp_prs.c1453
-rw-r--r--drivers/input/misc/ste_ff_vibra.c234
8 files changed, 3627 insertions, 71 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 7b46781c30c..cd69fd6164c 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -22,12 +22,26 @@ config INPUT_88PM860X_ONKEY
To compile this driver as a module, choose M here: the module
will be called 88pm860x_onkey.
+config INPUT_AB8500_ACCDET
+ bool "AB8500 AV Accessory detection"
+ depends on AB8500_CORE && AB8500_GPADC && GPIO_AB8500
+ help
+ Say Y here to enable AV accessory detection features for ST-Ericsson's
+ AB8500 Mix-Sig PMIC.
+
+config INPUT_AB5500_ACCDET
+ bool "AB5500 AV Accessory detection"
+ depends on AB5500_CORE && AB5500_GPADC
+ help
+ Say Y here to enable AV accessory detection features for ST-Ericsson's
+ AB5500 Mix-Sig PMIC.
+
config INPUT_AB8500_PONKEY
- tristate "AB8500 Pon (PowerOn) Key"
- depends on AB8500_CORE
+ tristate "AB5500/AB8500 Pon (PowerOn) Key"
+ depends on AB5500_CORE || AB8500_CORE
help
- Say Y here to use the PowerOn Key for ST-Ericsson's AB8500
- Mix-Sig PMIC.
+ Say Y here to use the PowerOn Key for ST-Ericsson's AB5500/AB8500
+ Mix-Sig PMICs.
To compile this driver as a module, choose M here: the module
will be called ab8500-ponkey.
@@ -300,6 +314,22 @@ config INPUT_KXTJ9_POLLED_MODE
help
Say Y here if you need accelerometer to work in polling mode.
+config INPUT_LPS001WP
+ tristate "LPS0001WP pressure sensor from ST Micro"
+ default y if MACH_U8500
+ help
+ This is a pressure sensor connected to I2C, mounted on the
+ snowball and other ST-E boards
+
+config LPS001WP_INPUT_DEVICE
+ bool "ST LPS001WP INPUT DEVICE"
+ depends on INPUT_LPS001WP
+ default n
+ help
+ This driver allows device to be used as an input device
+ need to be enabled only when input device support
+ is required.
+
config INPUT_POWERMATE
tristate "Griffin PowerMate and Contour Jog support"
depends on USB_ARCH_HAS_HCD
@@ -569,4 +599,14 @@ config INPUT_XEN_KBDDEV_FRONTEND
To compile this driver as a module, choose M here: the
module will be called xen-kbdfront.
+config INPUT_STE_FF_VIBRA
+ tristate "ST-Ericsson Force Feedback Vibrator"
+ depends on STE_AUDIO_IO_DEV
+ select INPUT_FF_MEMLESS
+ help
+ This option enables support for ST-Ericsson's Vibrator which
+ registers as an input force feedback driver.
+
+ To compile this driver as a module, choose M here. The module will
+ be called ste_ff_vibra.
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 46671a875b9..3a19186a7b1 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -5,6 +5,8 @@
# Each configuration option enables a list of files.
obj-$(CONFIG_INPUT_88PM860X_ONKEY) += 88pm860x_onkey.o
+obj-$(CONFIG_INPUT_AB8500_ACCDET) += abx500-accdet.o ab8500-accdet.o
+obj-$(CONFIG_INPUT_AB5500_ACCDET) += abx500-accdet.o ab5500-accdet.o
obj-$(CONFIG_INPUT_AB8500_PONKEY) += ab8500-ponkey.o
obj-$(CONFIG_INPUT_AD714X) += ad714x.o
obj-$(CONFIG_INPUT_AD714X_I2C) += ad714x-i2c.o
@@ -28,6 +30,7 @@ obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
+obj-$(CONFIG_INPUT_LPS001WP) += lps001wp_prs.o
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o
obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o
@@ -53,3 +56,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
+obj-$(CONFIG_INPUT_STE_FF_VIBRA) += ste_ff_vibra.o
diff --git a/drivers/input/misc/ab5500-accdet.c b/drivers/input/misc/ab5500-accdet.c
new file mode 100644
index 00000000000..7622f3b45e9
--- /dev/null
+++ b/drivers/input/misc/ab5500-accdet.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Jarmo K. Kuronen <jarmo.kuronen@symbio.com>
+ * for ST-Ericsson.
+ *
+ * License terms: GPL V2
+ *
+ * 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/module.h>
+#include <linux/delay.h>
+#include <linux/mfd/abx500/ab5500.h>
+#include <linux/mfd/abx500/ab5500-gpadc.h>
+#include <linux/mfd/abx500.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <mach/abx500-accdet.h>
+
+/*
+ * Register definition for accessory detection.
+ */
+#define AB5500_REGU_CTRL1_SPARE_REG 0x84
+#define AB5500_ACC_DET_DB1_REG 0x20
+#define AB5500_ACC_DET_DB2_REG 0x21
+#define AB5500_ACC_DET_CTRL_REG 0x23
+#define AB5500_VDENC_CTRL0 0x80
+
+/* REGISTER: AB8500_ACC_DET_CTRL_REG */
+#define BITS_ACCDETCTRL2_ENA (0x20 | 0x10 | 0x08)
+#define BITS_ACCDETCTRL1_ENA (0x02 | 0x01)
+
+/* REGISTER: AB8500_REGU_CTRL1_SPARE_REG */
+#define BIT_REGUCTRL1SPARE_VAMIC1_GROUND 0x01
+
+/* REGISTER: AB8500_IT_SOURCE5_REG */
+#define BIT_ITSOURCE5_ACCDET1 0x02
+
+static struct accessory_irq_descriptor ab5500_irq_desc[] = {
+ {
+ .irq = PLUG_IRQ,
+ .name = "acc_detedt1db_falling",
+ .isr = plug_irq_handler,
+ },
+ {
+ .irq = UNPLUG_IRQ,
+ .name = "acc_detedt1db_rising",
+ .isr = unplug_irq_handler,
+ },
+ {
+ .irq = BUTTON_PRESS_IRQ,
+ .name = "acc_detedt21db_falling",
+ .isr = button_press_irq_handler,
+ },
+ {
+ .irq = BUTTON_RELEASE_IRQ,
+ .name = "acc_detedt21db_rising",
+ .isr = button_release_irq_handler,
+ },
+};
+
+static struct accessory_regu_descriptor ab5500_regu_desc[] = {
+ {
+ .id = REGULATOR_VAMIC1,
+ .name = "v-amic",
+ },
+};
+
+
+/*
+ * configures accdet2 input on/off
+ */
+static void ab5500_config_accdetect2_hw(struct abx500_ad *dd, int enable)
+{
+ int ret = 0;
+
+ if (!dd->accdet2_th_set) {
+ /* Configure accdetect21+22 thresholds */
+ ret = abx500_set_register_interruptible(&dd->pdev->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_ACC_DET_DB2_REG,
+ dd->pdata->accdet2122_th);
+ if (ret < 0) {
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to write reg (%d).\n", __func__,
+ ret);
+ goto out;
+ } else {
+ dd->accdet2_th_set = 1;
+ }
+ }
+
+ /* Enable/Disable accdetect21 comparators + pullup */
+ ret = abx500_mask_and_set_register_interruptible(
+ &dd->pdev->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_ACC_DET_CTRL_REG,
+ BITS_ACCDETCTRL2_ENA,
+ enable ? BITS_ACCDETCTRL2_ENA : 0);
+
+ if (ret < 0)
+ dev_err(&dd->pdev->dev, "%s: Failed to update reg (%d).\n",
+ __func__, ret);
+out:
+ return;
+}
+
+/*
+ * configures accdet1 input on/off
+ */
+static void ab5500_config_accdetect1_hw(struct abx500_ad *dd, int enable)
+{
+ int ret;
+
+ if (!dd->accdet1_th_set) {
+ ret = abx500_set_register_interruptible(&dd->pdev->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_ACC_DET_DB1_REG,
+ dd->pdata->accdet1_dbth);
+ if (ret < 0)
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to write reg (%d).\n", __func__,
+ ret);
+ else
+ dd->accdet1_th_set = 1;
+ }
+
+ /* enable accdetect1 comparator */
+ ret = abx500_mask_and_set_register_interruptible(
+ &dd->pdev->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_ACC_DET_CTRL_REG,
+ BITS_ACCDETCTRL1_ENA,
+ enable ? BITS_ACCDETCTRL1_ENA : 0);
+
+ if (ret < 0)
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to update reg (%d).\n", __func__, ret);
+}
+
+/*
+ * returns the high level status whether some accessory is connected (1|0).
+ */
+static int ab5500_detect_plugged_in(struct abx500_ad *dd)
+{
+ u8 value = 0;
+
+ int status = abx500_get_register_interruptible(
+ &dd->pdev->dev,
+ AB5500_BANK_IT,
+ AB5500_IT_SOURCE3_REG,
+ &value);
+ if (status < 0) {
+ dev_err(&dd->pdev->dev, "%s: reg read failed (%d).\n",
+ __func__, status);
+ return 0;
+ }
+
+ if (dd->pdata->is_detection_inverted)
+ return value & BIT_ITSOURCE5_ACCDET1 ? 1 : 0;
+ else
+ return value & BIT_ITSOURCE5_ACCDET1 ? 0 : 1;
+}
+
+/*
+ * mic_line_voltage_stable - measures a relative stable voltage from spec. input
+ */
+static int ab5500_meas_voltage_stable(struct abx500_ad *dd)
+{
+ int iterations = 2;
+ int v1, v2, dv;
+
+ v1 = ab5500_gpadc_convert((struct ab5500_gpadc *)dd->gpadc,
+ ACC_DETECT2);
+ do {
+ msleep(1);
+ --iterations;
+ v2 = ab5500_gpadc_convert((struct ab5500_gpadc *)dd->gpadc,
+ ACC_DETECT2);
+ dv = abs(v2 - v1);
+ v1 = v2;
+ } while (iterations > 0 && dv > MAX_VOLT_DIFF);
+
+ return v1;
+}
+
+/*
+ * not implemented
+ */
+static int ab5500_meas_alt_voltage_stable(struct abx500_ad *dd)
+{
+ return -1;
+}
+
+/*
+ * configures HW so that it is possible to make decision whether
+ * accessory is connected or not.
+ */
+static void ab5500_config_hw_test_plug_connected(struct abx500_ad *dd,
+ int enable)
+{
+ dev_dbg(&dd->pdev->dev, "%s:%d\n", __func__, enable);
+
+ /* enable mic BIAS2 */
+ if (enable)
+ accessory_regulator_enable(dd, REGULATOR_VAMIC1);
+}
+
+/*
+ * configures HW so that carkit/headset detection can be accomplished.
+ */
+static void ab5500_config_hw_test_basic_carkit(struct abx500_ad *dd, int enable)
+{
+ /* enable mic BIAS2 */
+ if (enable)
+ accessory_regulator_disable(dd, REGULATOR_VAMIC1);
+}
+
+static u8 acc_det_ctrl_suspend_val;
+
+static void ab5500_turn_off_accdet_comparator(struct platform_device *pdev)
+{
+ struct abx500_ad *dd = platform_get_drvdata(pdev);
+
+ /* Turn off AccDetect comparators and pull-up */
+ (void) abx500_get_register_interruptible(
+ &dd->pdev->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_ACC_DET_CTRL_REG,
+ &acc_det_ctrl_suspend_val);
+ (void) abx500_set_register_interruptible(
+ &dd->pdev->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_ACC_DET_CTRL_REG,
+ 0);
+}
+
+static void ab5500_turn_on_accdet_comparator(struct platform_device *pdev)
+{
+ struct abx500_ad *dd = platform_get_drvdata(pdev);
+
+ /* Turn on AccDetect comparators and pull-up */
+ (void) abx500_set_register_interruptible(
+ &dd->pdev->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_ACC_DET_CTRL_REG,
+ acc_det_ctrl_suspend_val);
+}
+
+static void *ab5500_accdet_abx500_gpadc_get(void)
+{
+ return ab5500_gpadc_get("ab5500-adc.0");
+}
+
+struct abx500_accdet_platform_data *
+ ab5500_get_platform_data(struct platform_device *pdev)
+{
+ return pdev->dev.platform_data;
+}
+
+struct abx500_ad ab5500_accessory_det_callbacks = {
+ .irq_desc_norm = ab5500_irq_desc,
+ .irq_desc_inverted = NULL,
+ .no_irqs = ARRAY_SIZE(ab5500_irq_desc),
+ .regu_desc = ab5500_regu_desc,
+ .no_of_regu_desc = ARRAY_SIZE(ab5500_regu_desc),
+ .config_accdetect2_hw = ab5500_config_accdetect2_hw,
+ .config_accdetect1_hw = ab5500_config_accdetect1_hw,
+ .detect_plugged_in = ab5500_detect_plugged_in,
+ .meas_voltage_stable = ab5500_meas_voltage_stable,
+ .meas_alt_voltage_stable = ab5500_meas_alt_voltage_stable,
+ .config_hw_test_basic_carkit = ab5500_config_hw_test_basic_carkit,
+ .turn_off_accdet_comparator = ab5500_turn_off_accdet_comparator,
+ .turn_on_accdet_comparator = ab5500_turn_on_accdet_comparator,
+ .accdet_abx500_gpadc_get = ab5500_accdet_abx500_gpadc_get,
+ .config_hw_test_plug_connected = ab5500_config_hw_test_plug_connected,
+ .set_av_switch = NULL,
+ .get_platform_data = ab5500_get_platform_data,
+};
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/ab8500-accdet.c b/drivers/input/misc/ab8500-accdet.c
new file mode 100644
index 00000000000..0fe60364d54
--- /dev/null
+++ b/drivers/input/misc/ab8500-accdet.c
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Jarmo K. Kuronen <jarmo.kuronen@symbio.com>
+ * for ST-Ericsson.
+ *
+ * License terms: GPL V2
+ *
+ * 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/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/abx500.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/mfd/abx500/ab8500-gpio.h>
+#include <linux/gpio.h>
+#include <linux/err.h>
+#include <mach/abx500-accdet.h>
+#ifdef CONFIG_SND_SOC_UX500_AB8500
+#include <sound/ux500_ab8500_ext.h>
+#endif
+
+#define MAX_DET_COUNT 10
+#define MAX_VOLT_DIFF 30
+#define MIN_MIC_POWER -100
+
+/* Unique value used to identify Headset button input device */
+#define BTN_INPUT_UNIQUE_VALUE "AB8500HsBtn"
+#define BTN_INPUT_DEV_NAME "AB8500 Hs Button"
+
+#define DEBOUNCE_PLUG_EVENT_MS 100
+#define DEBOUNCE_PLUG_RETEST_MS 25
+#define DEBOUNCE_UNPLUG_EVENT_MS 0
+
+/*
+ * Register definition for accessory detection.
+ */
+#define AB8500_REGU_CTRL1_SPARE_REG 0x84
+#define AB8500_ACC_DET_DB1_REG 0x80
+#define AB8500_ACC_DET_DB2_REG 0x81
+#define AB8500_ACC_DET_CTRL_REG 0x82
+#define AB8500_IT_SOURCE5_REG 0x04
+
+/* REGISTER: AB8500_ACC_DET_CTRL_REG */
+#define BITS_ACCDETCTRL2_ENA (0x20 | 0x10 | 0x08)
+#define BITS_ACCDETCTRL1_ENA (0x02 | 0x01)
+
+/* REGISTER: AB8500_REGU_CTRL1_SPARE_REG */
+#define BIT_REGUCTRL1SPARE_VAMIC1_GROUND 0x01
+
+/* REGISTER: AB8500_IT_SOURCE5_REG */
+#define BIT_ITSOURCE5_ACCDET1 0x04
+
+/* After being loaded, how fast the first check is to be made */
+#define INIT_DELAY_MS 3000
+
+/* Voltage limits (mV) for various types of AV Accessories */
+#define ACCESSORY_DET_VOL_DONTCARE -1
+#define ACCESSORY_HEADPHONE_DET_VOL_MIN 0
+#define ACCESSORY_HEADPHONE_DET_VOL_MAX 40
+#define ACCESSORY_CVIDEO_DET_VOL_MIN 41
+#define ACCESSORY_CVIDEO_DET_VOL_MAX 105
+#define ACCESSORY_CARKIT_DET_VOL_MIN 1100
+#define ACCESSORY_CARKIT_DET_VOL_MAX 1300
+#define ACCESSORY_HEADSET_DET_VOL_MIN 0
+#define ACCESSORY_HEADSET_DET_VOL_MAX 200
+#define ACCESSORY_OPENCABLE_DET_VOL_MIN 1730
+#define ACCESSORY_OPENCABLE_DET_VOL_MAX 2150
+
+/* Static data initialization */
+
+static struct accessory_regu_descriptor ab8500_regu_desc[3] = {
+ {
+ .id = REGULATOR_VAUDIO,
+ .name = "v-audio",
+ },
+ {
+ .id = REGULATOR_VAMIC1,
+ .name = "v-amic1",
+ },
+ {
+ .id = REGULATOR_AVSWITCH,
+ .name = "vcc-N2158",
+ },
+};
+
+static struct accessory_irq_descriptor ab8500_irq_desc_norm[] = {
+ {
+ .irq = PLUG_IRQ,
+ .name = "ACC_DETECT_1DB_F",
+ .isr = plug_irq_handler,
+ },
+ {
+ .irq = UNPLUG_IRQ,
+ .name = "ACC_DETECT_1DB_R",
+ .isr = unplug_irq_handler,
+ },
+ {
+ .irq = BUTTON_PRESS_IRQ,
+ .name = "ACC_DETECT_22DB_F",
+ .isr = button_press_irq_handler,
+ },
+ {
+ .irq = BUTTON_RELEASE_IRQ,
+ .name = "ACC_DETECT_22DB_R",
+ .isr = button_release_irq_handler,
+ },
+};
+
+static struct accessory_irq_descriptor ab8500_irq_desc_inverted[] = {
+ {
+ .irq = PLUG_IRQ,
+ .name = "ACC_DETECT_1DB_R",
+ .isr = plug_irq_handler,
+ },
+ {
+ .irq = UNPLUG_IRQ,
+ .name = "ACC_DETECT_1DB_F",
+ .isr = unplug_irq_handler,
+ },
+ {
+ .irq = BUTTON_PRESS_IRQ,
+ .name = "ACC_DETECT_22DB_R",
+ .isr = button_press_irq_handler,
+ },
+ {
+ .irq = BUTTON_RELEASE_IRQ,
+ .name = "ACC_DETECT_22DB_F",
+ .isr = button_release_irq_handler,
+ },
+};
+
+/*
+ * configures accdet2 input on/off
+ */
+static void ab8500_config_accdetect2_hw(struct abx500_ad *dd, int enable)
+{
+ int ret = 0;
+
+ if (!dd->accdet2_th_set) {
+ /* Configure accdetect21+22 thresholds */
+ ret = abx500_set_register_interruptible(&dd->pdev->dev,
+ AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_DB2_REG,
+ dd->pdata->accdet2122_th);
+ if (ret < 0) {
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to write reg (%d).\n", __func__,
+ ret);
+ goto out;
+ } else {
+ dd->accdet2_th_set = 1;
+ }
+ }
+
+ /* Enable/Disable accdetect21 comparators + pullup */
+ ret = abx500_mask_and_set_register_interruptible(
+ &dd->pdev->dev,
+ AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ BITS_ACCDETCTRL2_ENA,
+ enable ? BITS_ACCDETCTRL2_ENA : 0);
+
+ if (ret < 0)
+ dev_err(&dd->pdev->dev, "%s: Failed to update reg (%d).\n",
+ __func__, ret);
+
+out:
+ return;
+}
+
+/*
+ * configures accdet1 input on/off
+ */
+static void ab8500_config_accdetect1_hw(struct abx500_ad *dd, int enable)
+{
+ int ret;
+
+ if (!dd->accdet1_th_set) {
+ ret = abx500_set_register_interruptible(&dd->pdev->dev,
+ AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_DB1_REG,
+ dd->pdata->accdet1_dbth);
+ if (ret < 0)
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to write reg (%d).\n", __func__,
+ ret);
+ else
+ dd->accdet1_th_set = 1;
+ }
+
+ /* enable accdetect1 comparator */
+ ret = abx500_mask_and_set_register_interruptible(
+ &dd->pdev->dev,
+ AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ BITS_ACCDETCTRL1_ENA,
+ enable ? BITS_ACCDETCTRL1_ENA : 0);
+
+ if (ret < 0)
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to update reg (%d).\n", __func__, ret);
+}
+
+/*
+ * returns the high level status whether some accessory is connected (1|0).
+ */
+static int ab8500_detect_plugged_in(struct abx500_ad *dd)
+{
+ u8 value = 0;
+
+ int status = abx500_get_register_interruptible(
+ &dd->pdev->dev,
+ AB8500_INTERRUPT,
+ AB8500_IT_SOURCE5_REG,
+ &value);
+ if (status < 0) {
+ dev_err(&dd->pdev->dev, "%s: reg read failed (%d).\n",
+ __func__, status);
+ return 0;
+ }
+
+ if (dd->pdata->is_detection_inverted)
+ return value & BIT_ITSOURCE5_ACCDET1 ? 1 : 0;
+ else
+ return value & BIT_ITSOURCE5_ACCDET1 ? 0 : 1;
+}
+
+#ifdef CONFIG_SND_SOC_UX500_AB8500
+
+/*
+ * meas_voltage_stable - measures relative stable voltage from spec. input
+ */
+static int ab8500_meas_voltage_stable(struct abx500_ad *dd)
+{
+ int ret, mv;
+
+ ret = ux500_ab8500_audio_gpadc_measure((struct ab8500_gpadc *)dd->gpadc,
+ ACC_DETECT2, false, &mv);
+
+ return (ret < 0) ? ret : mv;
+}
+
+/*
+ * meas_alt_voltage_stable - measures relative stable voltage from spec. input
+ */
+static int ab8500_meas_alt_voltage_stable(struct abx500_ad *dd)
+{
+ int ret, mv;
+
+ ret = ux500_ab8500_audio_gpadc_measure((struct ab8500_gpadc *)dd->gpadc,
+ ACC_DETECT2, true, &mv);
+
+ return (ret < 0) ? ret : mv;
+}
+
+#else
+
+/*
+ * meas_voltage_stable - measures relative stable voltage from spec. input
+ */
+static int ab8500_meas_voltage_stable(struct abx500_ad *dd)
+{
+ int iterations = 2;
+ int v1, v2, dv;
+
+ v1 = ab8500_gpadc_convert((struct ab8500_gpadc *)dd->gpadc,
+ ACC_DETECT2);
+ do {
+ msleep(1);
+ --iterations;
+ v2 = ab8500_gpadc_convert((struct ab8500_gpadc *)dd->gpadc,
+ ACC_DETECT2);
+ dv = abs(v2 - v1);
+ v1 = v2;
+ } while (iterations > 0 && dv > MAX_VOLT_DIFF);
+
+ return v1;
+}
+
+/*
+ * not implemented for non soc setups
+ */
+static int ab8500_meas_alt_voltage_stable(struct abx500_ad *dd)
+{
+ return -1;
+}
+
+#endif
+
+/*
+ * configures HW so that it is possible to make decision whether
+ * accessory is connected or not.
+ */
+static void ab8500_config_hw_test_plug_connected(struct abx500_ad *dd,
+ int enable)
+{
+ int ret;
+
+ dev_dbg(&dd->pdev->dev, "%s:%d\n", __func__, enable);
+
+ ret = ab8500_config_pulldown(&dd->pdev->dev,
+ dd->pdata->video_ctrl_gpio, !enable);
+ if (ret < 0) {
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to update reg (%d).\n", __func__, ret);
+ return;
+ }
+
+ if (enable)
+ accessory_regulator_enable(dd, REGULATOR_VAMIC1);
+}
+
+/*
+ * configures HW so that carkit/headset detection can be accomplished.
+ */
+static void ab8500_config_hw_test_basic_carkit(struct abx500_ad *dd, int enable)
+{
+ int ret;
+
+ dev_dbg(&dd->pdev->dev, "%s:%d\n", __func__, enable);
+
+ if (enable)
+ accessory_regulator_disable(dd, REGULATOR_VAMIC1);
+
+ /* Un-Ground the VAMic1 output when enabled */
+ ret = abx500_mask_and_set_register_interruptible(
+ &dd->pdev->dev,
+ AB8500_REGU_CTRL1,
+ AB8500_REGU_CTRL1_SPARE_REG,
+ BIT_REGUCTRL1SPARE_VAMIC1_GROUND,
+ enable ? BIT_REGUCTRL1SPARE_VAMIC1_GROUND : 0);
+ if (ret < 0)
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to update reg (%d).\n", __func__, ret);
+}
+
+/*
+ * sets the av switch direction - audio-in vs video-out
+ */
+static void ab8500_set_av_switch(struct abx500_ad *dd,
+ enum accessory_avcontrol_dir dir)
+{
+ int ret;
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter (%d)\n", __func__, dir);
+ if (dir == NOT_SET) {
+ ret = gpio_direction_input(dd->pdata->video_ctrl_gpio);
+ dd->gpio35_dir_set = 0;
+ ret = gpio_direction_output(dd->pdata->video_ctrl_gpio, 0);
+ } else if (!dd->gpio35_dir_set) {
+ ret = gpio_direction_output(dd->pdata->video_ctrl_gpio,
+ dir == AUDIO_IN ? 1 : 0);
+ if (ret < 0) {
+ dev_err(&dd->pdev->dev,
+ "%s: Output video ctrl signal failed (%d).\n",
+ __func__, ret);
+ } else {
+ dd->gpio35_dir_set = 1;
+ dev_dbg(&dd->pdev->dev, "AV-SWITCH: %s\n",
+ dir == AUDIO_IN ? "AUDIO_IN" : "VIDEO_OUT");
+ }
+ } else {
+ gpio_set_value(dd->pdata->video_ctrl_gpio,
+ dir == AUDIO_IN ? 1 : 0);
+ }
+}
+
+static u8 acc_det_ctrl_suspend_val;
+
+static void ab8500_turn_off_accdet_comparator(struct platform_device *pdev)
+{
+ struct abx500_ad *dd = platform_get_drvdata(pdev);
+
+ /* Turn off AccDetect comparators and pull-up */
+ (void) abx500_get_register_interruptible(
+ &dd->pdev->dev,
+ AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ &acc_det_ctrl_suspend_val);
+ (void) abx500_set_register_interruptible(
+ &dd->pdev->dev,
+ AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ 0);
+
+}
+
+static void ab8500_turn_on_accdet_comparator(struct platform_device *pdev)
+{
+ struct abx500_ad *dd = platform_get_drvdata(pdev);
+
+ /* Turn on AccDetect comparators and pull-up */
+ (void) abx500_set_register_interruptible(
+ &dd->pdev->dev,
+ AB8500_ECI_AV_ACC,
+ AB8500_ACC_DET_CTRL_REG,
+ acc_det_ctrl_suspend_val);
+
+}
+
+static void *ab8500_accdet_abx500_gpadc_get(void)
+{
+ return ab8500_gpadc_get();
+}
+
+struct abx500_accdet_platform_data *
+ ab8500_get_platform_data(struct platform_device *pdev)
+{
+ struct ab8500_platform_data *plat;
+
+ plat = dev_get_platdata(pdev->dev.parent);
+
+ if (!plat || !plat->accdet) {
+ dev_err(&pdev->dev, "%s: Failed to get accdet plat data.\n",
+ __func__);
+ return ERR_PTR(-ENODEV);
+ }
+
+ return plat->accdet;
+}
+
+struct abx500_ad ab8500_accessory_det_callbacks = {
+ .irq_desc_norm = ab8500_irq_desc_norm,
+ .irq_desc_inverted = ab8500_irq_desc_inverted,
+ .no_irqs = ARRAY_SIZE(ab8500_irq_desc_norm),
+ .regu_desc = ab8500_regu_desc,
+ .no_of_regu_desc = ARRAY_SIZE(ab8500_regu_desc),
+ .config_accdetect2_hw = ab8500_config_accdetect2_hw,
+ .config_accdetect1_hw = ab8500_config_accdetect1_hw,
+ .detect_plugged_in = ab8500_detect_plugged_in,
+ .meas_voltage_stable = ab8500_meas_voltage_stable,
+ .meas_alt_voltage_stable = ab8500_meas_alt_voltage_stable,
+ .config_hw_test_basic_carkit = ab8500_config_hw_test_basic_carkit,
+ .turn_off_accdet_comparator = ab8500_turn_off_accdet_comparator,
+ .turn_on_accdet_comparator = ab8500_turn_on_accdet_comparator,
+ .accdet_abx500_gpadc_get = ab8500_accdet_abx500_gpadc_get,
+ .config_hw_test_plug_connected = ab8500_config_hw_test_plug_connected,
+ .set_av_switch = ab8500_set_av_switch,
+ .get_platform_data = ab8500_get_platform_data,
+};
+
+MODULE_DESCRIPTION("AB8500 AV Accessory detection driver");
+MODULE_ALIAS("platform:ab8500-acc-det");
+MODULE_AUTHOR("ST-Ericsson");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/ab8500-ponkey.c b/drivers/input/misc/ab8500-ponkey.c
index 350fd0c385d..c3c3c51d302 100644
--- a/drivers/input/misc/ab8500-ponkey.c
+++ b/drivers/input/misc/ab8500-ponkey.c
@@ -6,7 +6,6 @@
*
* AB8500 Power-On Key handler
*/
-
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -14,128 +13,208 @@
#include <linux/interrupt.h>
#include <linux/mfd/abx500/ab8500.h>
#include <linux/slab.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab5500.h>
+
+/* Ponkey time control bits */
+#define AB5500_MCB 0x2F
+#define AB5500_PONKEY_10SEC 0x0
+#define AB5500_PONKEY_5SEC 0x1
+#define AB5500_PONKEY_DISABLE 0x2
+#define AB5500_PONKEY_TMR_MASK 0x1
+#define AB5500_PONKEY_TR_MASK 0x2
+
+static int ab5500_ponkey_hw_init(struct platform_device *);
+
+struct ab8500_ponkey_variant {
+ const char *irq_falling;
+ const char *irq_rising;
+ int (*hw_init)(struct platform_device *);
+};
+
+static const struct ab8500_ponkey_variant ab5500_onswa = {
+ .irq_falling = "ONSWAn_falling",
+ .irq_rising = "ONSWAn_rising",
+ .hw_init = ab5500_ponkey_hw_init,
+};
+
+static const struct ab8500_ponkey_variant ab8500_ponkey = {
+ .irq_falling = "ONKEY_DBF",
+ .irq_rising = "ONKEY_DBR",
+};
/**
- * struct ab8500_ponkey - ab8500 ponkey information
+ * struct ab8500_ponkey_info - ab8500 ponkey information
* @input_dev: pointer to input device
- * @ab8500: ab8500 parent
* @irq_dbf: irq number for falling transition
* @irq_dbr: irq number for rising transition
*/
-struct ab8500_ponkey {
+struct ab8500_ponkey_info {
struct input_dev *idev;
- struct ab8500 *ab8500;
int irq_dbf;
int irq_dbr;
};
+static int ab5500_ponkey_hw_init(struct platform_device *pdev)
+{
+ u8 val;
+ struct ab5500_ponkey_platform_data *pdata;
+
+ pdata = pdev->dev.platform_data;
+ if (pdata) {
+ switch (pdata->shutdown_secs) {
+ case 0:
+ val = AB5500_PONKEY_DISABLE;
+ break;
+ case 5:
+ val = AB5500_PONKEY_5SEC;
+ break;
+ case 10:
+ val = AB5500_PONKEY_10SEC;
+ break;
+ default:
+ val = AB5500_PONKEY_10SEC;
+ }
+ } else {
+ val = AB5500_PONKEY_10SEC;
+ }
+ return abx500_mask_and_set(
+ &pdev->dev,
+ AB5500_BANK_STARTUP,
+ AB5500_MCB,
+ AB5500_PONKEY_TMR_MASK | AB5500_PONKEY_TR_MASK,
+ val);
+}
+
/* AB8500 gives us an interrupt when ONKEY is held */
static irqreturn_t ab8500_ponkey_handler(int irq, void *data)
{
- struct ab8500_ponkey *ponkey = data;
+ struct ab8500_ponkey_info *info = data;
- if (irq == ponkey->irq_dbf)
- input_report_key(ponkey->idev, KEY_POWER, true);
- else if (irq == ponkey->irq_dbr)
- input_report_key(ponkey->idev, KEY_POWER, false);
+ if (irq == info->irq_dbf)
+ input_report_key(info->idev, KEY_POWER, true);
+ else if (irq == info->irq_dbr)
+ input_report_key(info->idev, KEY_POWER, false);
- input_sync(ponkey->idev);
+ input_sync(info->idev);
return IRQ_HANDLED;
}
static int __devinit ab8500_ponkey_probe(struct platform_device *pdev)
{
- struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent);
- struct ab8500_ponkey *ponkey;
- struct input_dev *input;
- int irq_dbf, irq_dbr;
- int error;
+ const struct ab8500_ponkey_variant *variant;
+ struct ab8500_ponkey_info *info;
+ int irq_dbf, irq_dbr, ret;
+
+ variant = (const struct ab8500_ponkey_variant *)
+ pdev->id_entry->driver_data;
+
+ if (variant->hw_init) {
+ ret = variant->hw_init(pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to init hw");
+ return ret;
+ }
+ }
- irq_dbf = platform_get_irq_byname(pdev, "ONKEY_DBF");
+ irq_dbf = platform_get_irq_byname(pdev, variant->irq_falling);
if (irq_dbf < 0) {
- dev_err(&pdev->dev, "No IRQ for ONKEY_DBF, error=%d\n", irq_dbf);
+ dev_err(&pdev->dev, "No IRQ for %s: %d\n",
+ variant->irq_falling, irq_dbf);
return irq_dbf;
}
- irq_dbr = platform_get_irq_byname(pdev, "ONKEY_DBR");
+ irq_dbr = platform_get_irq_byname(pdev, variant->irq_rising);
if (irq_dbr < 0) {
- dev_err(&pdev->dev, "No IRQ for ONKEY_DBR, error=%d\n", irq_dbr);
+ dev_err(&pdev->dev, "No IRQ for %s: %d\n",
+ variant->irq_rising, irq_dbr);
return irq_dbr;
}
- ponkey = kzalloc(sizeof(struct ab8500_ponkey), GFP_KERNEL);
- input = input_allocate_device();
- if (!ponkey || !input) {
- error = -ENOMEM;
- goto err_free_mem;
- }
+ info = kzalloc(sizeof(struct ab8500_ponkey_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
- ponkey->idev = input;
- ponkey->ab8500 = ab8500;
- ponkey->irq_dbf = irq_dbf;
- ponkey->irq_dbr = irq_dbr;
+ info->irq_dbf = irq_dbf;
+ info->irq_dbr = irq_dbr;
- input->name = "AB8500 POn(PowerOn) Key";
- input->dev.parent = &pdev->dev;
+ info->idev = input_allocate_device();
+ if (!info->idev) {
+ dev_err(&pdev->dev, "Failed to allocate input dev\n");
+ ret = -ENOMEM;
+ goto out;
+ }
- input_set_capability(input, EV_KEY, KEY_POWER);
+ info->idev->name = "AB8500 POn(PowerOn) Key";
+ info->idev->dev.parent = &pdev->dev;
+ info->idev->evbit[0] = BIT_MASK(EV_KEY);
+ info->idev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
- error = request_any_context_irq(ponkey->irq_dbf, ab8500_ponkey_handler,
- 0, "ab8500-ponkey-dbf", ponkey);
- if (error < 0) {
- dev_err(ab8500->dev, "Failed to request dbf IRQ#%d: %d\n",
- ponkey->irq_dbf, error);
- goto err_free_mem;
+ ret = input_register_device(info->idev);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't register input device: %d\n", ret);
+ goto out_unfreedevice;
}
- error = request_any_context_irq(ponkey->irq_dbr, ab8500_ponkey_handler,
- 0, "ab8500-ponkey-dbr", ponkey);
- if (error < 0) {
- dev_err(ab8500->dev, "Failed to request dbr IRQ#%d: %d\n",
- ponkey->irq_dbr, error);
- goto err_free_dbf_irq;
+ ret = request_threaded_irq(info->irq_dbf, NULL, ab8500_ponkey_handler,
+ IRQF_NO_SUSPEND, "ab8500-ponkey-dbf",
+ info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to request dbf IRQ#%d: %d\n",
+ info->irq_dbf, ret);
+ goto out_unregisterdevice;
}
- error = input_register_device(ponkey->idev);
- if (error) {
- dev_err(ab8500->dev, "Can't register input device: %d\n", error);
- goto err_free_dbr_irq;
+ ret = request_threaded_irq(info->irq_dbr, NULL, ab8500_ponkey_handler,
+ IRQF_NO_SUSPEND, "ab8500-ponkey-dbr",
+ info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to request dbr IRQ#%d: %d\n",
+ info->irq_dbr, ret);
+ goto out_irq_dbf;
}
- platform_set_drvdata(pdev, ponkey);
- return 0;
+ platform_set_drvdata(pdev, info);
-err_free_dbr_irq:
- free_irq(ponkey->irq_dbr, ponkey);
-err_free_dbf_irq:
- free_irq(ponkey->irq_dbf, ponkey);
-err_free_mem:
- input_free_device(input);
- kfree(ponkey);
+ return 0;
- return error;
+out_irq_dbf:
+ free_irq(info->irq_dbf, info);
+out_unregisterdevice:
+ input_unregister_device(info->idev);
+ info->idev = NULL;
+out_unfreedevice:
+ input_free_device(info->idev);
+out:
+ kfree(info);
+ return ret;
}
static int __devexit ab8500_ponkey_remove(struct platform_device *pdev)
{
- struct ab8500_ponkey *ponkey = platform_get_drvdata(pdev);
-
- free_irq(ponkey->irq_dbf, ponkey);
- free_irq(ponkey->irq_dbr, ponkey);
- input_unregister_device(ponkey->idev);
- kfree(ponkey);
-
- platform_set_drvdata(pdev, NULL);
+ struct ab8500_ponkey_info *info = platform_get_drvdata(pdev);
+ free_irq(info->irq_dbf, info);
+ free_irq(info->irq_dbr, info);
+ input_unregister_device(info->idev);
+ kfree(info);
return 0;
}
+static struct platform_device_id ab8500_ponkey_id_table[] = {
+ { "ab5500-onswa", (kernel_ulong_t)&ab5500_onswa, },
+ { "ab8500-poweron-key", (kernel_ulong_t)&ab8500_ponkey, },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, ab8500_ponkey_id_table);
+
static struct platform_driver ab8500_ponkey_driver = {
.driver = {
.name = "ab8500-poweron-key",
.owner = THIS_MODULE,
},
+ .id_table = ab8500_ponkey_id_table,
.probe = ab8500_ponkey_probe,
.remove = __devexit_p(ab8500_ponkey_remove),
};
diff --git a/drivers/input/misc/abx500-accdet.c b/drivers/input/misc/abx500-accdet.c
new file mode 100644
index 00000000000..d716a1d8870
--- /dev/null
+++ b/drivers/input/misc/abx500-accdet.c
@@ -0,0 +1,1011 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Jarmo K. Kuronen <jarmo.kuronen@symbio.com>
+ * for ST-Ericsson.
+ *
+ * License terms: GPL V2
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/workqueue.h>
+#include <linux/irq.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio.h>
+#include <linux/mfd/abx500.h>
+#include <linux/interrupt.h>
+#include <sound/jack.h>
+#include <mach/abx500-accdet.h>
+#ifdef CONFIG_SND_SOC_UX500_AB8500
+#include <sound/ux500_ab8500.h>
+#else
+#define ux500_ab8500_jack_report(i)
+#endif
+
+/* Unique value used to identify Headset button input device */
+#define BTN_INPUT_UNIQUE_VALUE "AB8500HsBtn"
+#define BTN_INPUT_DEV_NAME "AB8500 Hs Button"
+
+#define DEBOUNCE_PLUG_EVENT_MS 100
+#define DEBOUNCE_PLUG_RETEST_MS 25
+#define DEBOUNCE_UNPLUG_EVENT_MS 0
+
+/* After being loaded, how fast the first check is to be made */
+#define INIT_DELAY_MS 3000
+
+/* Voltage limits (mV) for various types of AV Accessories */
+#define ACCESSORY_DET_VOL_DONTCARE -1
+#define ACCESSORY_HEADPHONE_DET_VOL_MIN 0
+#define ACCESSORY_HEADPHONE_DET_VOL_MAX 40
+#define ACCESSORY_U_HEADSET_DET_VOL_MIN 47
+#define ACCESSORY_U_HEADSET_DET_VOL_MAX 732
+#define ACCESSORY_U_HEADSET_ALT_DET_VOL_MIN 25
+#define ACCESSORY_U_HEADSET_ALT_DET_VOL_MAX 50
+#define ACCESSORY_CVIDEO_DET_VOL_MIN 41
+#define ACCESSORY_CVIDEO_DET_VOL_MAX 105
+#define ACCESSORY_CARKIT_DET_VOL_MIN 1100
+#define ACCESSORY_CARKIT_DET_VOL_MAX 1300
+#define ACCESSORY_HEADSET_DET_VOL_MIN 1301
+#define ACCESSORY_HEADSET_DET_VOL_MAX 2000
+#define ACCESSORY_OPENCABLE_DET_VOL_MIN 2001
+#define ACCESSORY_OPENCABLE_DET_VOL_MAX 2150
+
+
+/* Macros */
+
+/*
+ * Conviniency macros to check jack characteristics.
+ */
+#define jack_supports_mic(type) \
+ (type == JACK_TYPE_HEADSET || type == JACK_TYPE_CARKIT)
+#define jack_supports_spkr(type) \
+ ((type != JACK_TYPE_DISCONNECTED) && (type != JACK_TYPE_CONNECTED))
+#define jack_supports_buttons(type) \
+ ((type == JACK_TYPE_HEADSET) ||\
+ (type == JACK_TYPE_CARKIT) ||\
+ (type == JACK_TYPE_OPENCABLE) ||\
+ (type == JACK_TYPE_CONNECTED))
+
+
+/* Forward declarations */
+static void config_accdetect(struct abx500_ad *dd);
+static enum accessory_jack_type detect(struct abx500_ad *dd, int *required_det);
+
+/* Static data initialization */
+static struct accessory_detect_task detect_ops[] = {
+ {
+ .type = JACK_TYPE_DISCONNECTED,
+ .typename = "DISCONNECTED",
+ .meas_mv = 1,
+ .req_det_count = 1,
+ .minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .maxvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE
+ },
+ {
+ .type = JACK_TYPE_HEADPHONE,
+ .typename = "HEADPHONE",
+ .meas_mv = 1,
+ .req_det_count = 1,
+ .minvol = ACCESSORY_HEADPHONE_DET_VOL_MIN,
+ .maxvol = ACCESSORY_HEADPHONE_DET_VOL_MAX,
+ .alt_minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE
+ },
+ {
+ .type = JACK_TYPE_UNSUPPORTED_HEADSET,
+ .typename = "UNSUPPORTED HEADSET",
+ .meas_mv = 1,
+ .req_det_count = 2,
+ .minvol = ACCESSORY_U_HEADSET_DET_VOL_MIN,
+ .maxvol = ACCESSORY_U_HEADSET_DET_VOL_MAX,
+ .alt_minvol = ACCESSORY_U_HEADSET_ALT_DET_VOL_MIN,
+ .alt_maxvol = ACCESSORY_U_HEADSET_ALT_DET_VOL_MAX
+ },
+ {
+ .type = JACK_TYPE_CVIDEO,
+ .typename = "CVIDEO",
+ .meas_mv = 0,
+ .req_det_count = 4,
+ .minvol = ACCESSORY_CVIDEO_DET_VOL_MIN,
+ .maxvol = ACCESSORY_CVIDEO_DET_VOL_MAX,
+ .alt_minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE
+ },
+ {
+ .type = JACK_TYPE_OPENCABLE,
+ .typename = "OPENCABLE",
+ .meas_mv = 0,
+ .req_det_count = 4,
+ .minvol = ACCESSORY_OPENCABLE_DET_VOL_MIN,
+ .maxvol = ACCESSORY_OPENCABLE_DET_VOL_MAX,
+ .alt_minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE
+ },
+ {
+ .type = JACK_TYPE_CARKIT,
+ .typename = "CARKIT",
+ .meas_mv = 1,
+ .req_det_count = 1,
+ .minvol = ACCESSORY_CARKIT_DET_VOL_MIN,
+ .maxvol = ACCESSORY_CARKIT_DET_VOL_MAX,
+ .alt_minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE
+ },
+ {
+ .type = JACK_TYPE_HEADSET,
+ .typename = "HEADSET",
+ .meas_mv = 0,
+ .req_det_count = 2,
+ .minvol = ACCESSORY_HEADSET_DET_VOL_MIN,
+ .maxvol = ACCESSORY_HEADSET_DET_VOL_MAX,
+ .alt_minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE
+ },
+ {
+ .type = JACK_TYPE_CONNECTED,
+ .typename = "CONNECTED",
+ .meas_mv = 0,
+ .req_det_count = 4,
+ .minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .maxvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_minvol = ACCESSORY_DET_VOL_DONTCARE,
+ .alt_maxvol = ACCESSORY_DET_VOL_DONTCARE
+ }
+};
+
+static struct accessory_irq_descriptor *abx500_accdet_irq_desc;
+
+/*
+ * textual represenation of the accessory type
+ */
+static const char *accessory_str(enum accessory_jack_type type)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(detect_ops); i++)
+ if (type == detect_ops[i].type)
+ return detect_ops[i].typename;
+
+ return "UNKNOWN?";
+}
+
+/*
+ * enables regulator but only if it has not been enabled earlier.
+ */
+void accessory_regulator_enable(struct abx500_ad *dd,
+ enum accessory_regulator reg)
+{
+ int i;
+
+ for (i = 0; i < dd->no_of_regu_desc; i++) {
+ if (reg & dd->regu_desc[i].id) {
+ if (!dd->regu_desc[i].enabled) {
+ if (!regulator_enable(dd->regu_desc[i].handle))
+ dd->regu_desc[i].enabled = 1;
+ }
+ }
+ }
+}
+
+/*
+ * disables regulator but only if it has been previously enabled.
+ */
+void accessory_regulator_disable(struct abx500_ad *dd,
+ enum accessory_regulator reg)
+{
+ int i;
+
+ for (i = 0; i < dd->no_of_regu_desc; i++) {
+ if (reg & dd->regu_desc[i].id) {
+ if (dd->regu_desc[i].enabled) {
+ if (!regulator_disable(dd->regu_desc[i].handle))
+ dd->regu_desc[i].enabled = 0;
+ }
+ }
+ }
+}
+
+/*
+ * frees previously retrieved regulators.
+ */
+static void free_regulators(struct abx500_ad *dd)
+{
+ int i;
+
+ for (i = 0; i < dd->no_of_regu_desc; i++) {
+ if (dd->regu_desc[i].handle) {
+ regulator_put(dd->regu_desc[i].handle);
+ dd->regu_desc[i].handle = NULL;
+ }
+ }
+}
+
+/*
+ * gets required regulators.
+ */
+static int create_regulators(struct abx500_ad *dd)
+{
+ int i;
+ int status = 0;
+
+ for (i = 0; i < dd->no_of_regu_desc; i++) {
+ struct regulator *regu =
+ regulator_get(&dd->pdev->dev, dd->regu_desc[i].name);
+ if (IS_ERR(regu)) {
+ status = PTR_ERR(regu);
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to get supply '%s' (%d).\n",
+ __func__, dd->regu_desc[i].name, status);
+ free_regulators(dd);
+ goto out;
+ } else {
+ dd->regu_desc[i].handle = regu;
+ }
+ }
+
+out:
+ return status;
+}
+
+/*
+ * create input device for button press reporting
+ */
+static int create_btn_input_dev(struct abx500_ad *dd)
+{
+ int err;
+
+ dd->btn_input_dev = input_allocate_device();
+ if (!dd->btn_input_dev) {
+ dev_err(&dd->pdev->dev, "%s: Failed to allocate input dev.\n",
+ __func__);
+ err = -ENOMEM;
+ goto out;
+ }
+
+ input_set_capability(dd->btn_input_dev,
+ EV_KEY,
+ dd->pdata->btn_keycode);
+
+ dd->btn_input_dev->name = BTN_INPUT_DEV_NAME;
+ dd->btn_input_dev->uniq = BTN_INPUT_UNIQUE_VALUE;
+ dd->btn_input_dev->dev.parent = &dd->pdev->dev;
+
+ err = input_register_device(dd->btn_input_dev);
+ if (err) {
+ dev_err(&dd->pdev->dev,
+ "%s: register_input_device failed (%d).\n", __func__,
+ err);
+ input_free_device(dd->btn_input_dev);
+ dd->btn_input_dev = NULL;
+ goto out;
+ }
+out:
+ return err;
+}
+
+/*
+ * reports jack status
+ */
+void report_jack_status(struct abx500_ad *dd)
+{
+ int value = 0;
+
+ /* Never report possible open cable */
+ if (dd->jack_type == JACK_TYPE_OPENCABLE)
+ goto out;
+
+ /* Never report same state twice in a row */
+ if (dd->jack_type == dd->reported_jack_type)
+ goto out;
+ dd->reported_jack_type = dd->jack_type;
+
+ dev_info(&dd->pdev->dev, "Accessory: %s\n",
+ accessory_str(dd->jack_type));
+
+ /* Never report unsupported headset */
+ if (dd->jack_type == JACK_TYPE_UNSUPPORTED_HEADSET)
+ goto out;
+
+ if (dd->jack_type != JACK_TYPE_DISCONNECTED &&
+ dd->jack_type != JACK_TYPE_UNSPECIFIED)
+ value |= SND_JACK_MECHANICAL;
+ if (jack_supports_mic(dd->jack_type))
+ value |= SND_JACK_MICROPHONE;
+ if (jack_supports_spkr(dd->jack_type))
+ value |= (SND_JACK_HEADPHONE | SND_JACK_LINEOUT);
+ if (dd->jack_type == JACK_TYPE_CVIDEO) {
+ value |= SND_JACK_VIDEOOUT;
+ if (dd->set_av_switch)
+ dd->set_av_switch(dd, VIDEO_OUT);
+ }
+ ux500_ab8500_jack_report(value);
+
+out: return;
+}
+
+/*
+ * worker routine to handle accessory unplug case
+ */
+void unplug_irq_handler_work(struct work_struct *work)
+{
+ struct abx500_ad *dd = container_of(work,
+ struct abx500_ad, unplug_irq_work.work);
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__);
+
+ dd->jack_type = dd->jack_type_temp = JACK_TYPE_DISCONNECTED;
+ dd->jack_det_count = dd->total_jack_det_count = 0;
+ dd->btn_state = BUTTON_UNK;
+ config_accdetect(dd);
+
+ accessory_regulator_disable(dd, REGULATOR_ALL);
+
+ report_jack_status(dd);
+}
+
+/*
+ * interrupt service routine for accessory unplug.
+ */
+irqreturn_t unplug_irq_handler(int irq, void *_userdata)
+{
+ struct abx500_ad *dd = _userdata;
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter (irq=%d)\n", __func__, irq);
+
+ queue_delayed_work(dd->irq_work_queue, &dd->unplug_irq_work,
+ msecs_to_jiffies(DEBOUNCE_UNPLUG_EVENT_MS));
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * interrupt service routine for accessory plug.
+ */
+irqreturn_t plug_irq_handler(int irq, void *_userdata)
+{
+ struct abx500_ad *dd = _userdata;
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter (irq=%d)\n",
+ __func__, irq);
+
+ switch (dd->jack_type) {
+ case JACK_TYPE_DISCONNECTED:
+ case JACK_TYPE_UNSPECIFIED:
+ queue_delayed_work(dd->irq_work_queue, &dd->detect_work,
+ msecs_to_jiffies(DEBOUNCE_PLUG_EVENT_MS));
+ break;
+
+ default:
+ dev_err(&dd->pdev->dev, "%s: Unexpected plug IRQ\n", __func__);
+ break;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * worker routine to perform detection.
+ */
+static void detect_work(struct work_struct *work)
+{
+ int req_det_count = 1;
+ enum accessory_jack_type new_type;
+ struct abx500_ad *dd = container_of(work,
+ struct abx500_ad, detect_work.work);
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__);
+
+ if (dd->set_av_switch)
+ dd->set_av_switch(dd, AUDIO_IN);
+
+ new_type = detect(dd, &req_det_count);
+
+ dd->total_jack_det_count++;
+ if (dd->jack_type_temp == new_type) {
+ dd->jack_det_count++;
+ } else {
+ dd->jack_det_count = 1;
+ dd->jack_type_temp = new_type;
+ }
+
+ if (dd->total_jack_det_count >= MAX_DET_COUNT) {
+ dev_err(&dd->pdev->dev,
+ "%s: MAX_DET_COUNT(=%d) reached. Bailing out.\n",
+ __func__, MAX_DET_COUNT);
+ queue_delayed_work(dd->irq_work_queue, &dd->unplug_irq_work,
+ msecs_to_jiffies(DEBOUNCE_UNPLUG_EVENT_MS));
+ } else if (dd->jack_det_count >= req_det_count) {
+ dd->total_jack_det_count = dd->jack_det_count = 0;
+ dd->jack_type = new_type;
+ dd->detect_jiffies = jiffies;
+ report_jack_status(dd);
+ config_accdetect(dd);
+ } else {
+ queue_delayed_work(dd->irq_work_queue,
+ &dd->detect_work,
+ msecs_to_jiffies(DEBOUNCE_PLUG_RETEST_MS));
+ }
+}
+
+/*
+ * reports a button event (pressed, released).
+ */
+static void report_btn_event(struct abx500_ad *dd, int down)
+{
+ input_report_key(dd->btn_input_dev, dd->pdata->btn_keycode, down);
+ input_sync(dd->btn_input_dev);
+
+ dev_dbg(&dd->pdev->dev, "HS-BTN: %s\n", down ? "PRESSED" : "RELEASED");
+}
+
+/*
+ * interrupt service routine invoked when hs button is pressed down.
+ */
+irqreturn_t button_press_irq_handler(int irq, void *_userdata)
+{
+ struct abx500_ad *dd = _userdata;
+
+ unsigned long accept_jiffies = dd->detect_jiffies +
+ msecs_to_jiffies(1000);
+ if (time_before(jiffies, accept_jiffies)) {
+ dev_dbg(&dd->pdev->dev, "%s: Skipped spurious btn press.\n",
+ __func__);
+ return IRQ_HANDLED;
+ }
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter (irq=%d)\n", __func__, irq);
+
+ if (dd->jack_type == JACK_TYPE_OPENCABLE) {
+ /* Someting got connected to open cable -> detect.. */
+ dd->config_accdetect2_hw(dd, 0);
+ queue_delayed_work(dd->irq_work_queue, &dd->detect_work,
+ msecs_to_jiffies(DEBOUNCE_PLUG_EVENT_MS));
+ return IRQ_HANDLED;
+ }
+
+ if (dd->btn_state == BUTTON_PRESSED)
+ return IRQ_HANDLED;
+
+ if (jack_supports_buttons(dd->jack_type)) {
+ dd->btn_state = BUTTON_PRESSED;
+ report_btn_event(dd, 1);
+ } else {
+ dd->btn_state = BUTTON_UNK;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * interrupts service routine invoked when hs button is released.
+ */
+irqreturn_t button_release_irq_handler(int irq, void *_userdata)
+{
+ struct abx500_ad *dd = _userdata;
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter (irq=%d)\n", __func__, irq);
+
+ if (dd->jack_type == JACK_TYPE_OPENCABLE)
+ return IRQ_HANDLED;
+
+ if (dd->btn_state != BUTTON_PRESSED)
+ return IRQ_HANDLED;
+
+ if (jack_supports_buttons(dd->jack_type)) {
+ report_btn_event(dd, 0);
+ dd->btn_state = BUTTON_RELEASED;
+ } else {
+ dd->btn_state = BUTTON_UNK;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * checks whether measured voltage is in given range. depending on arguments,
+ * voltage might be re-measured or previously measured voltage is reused.
+ */
+static int mic_vol_in_range(struct abx500_ad *dd,
+ int lo, int hi, int alt_lo, int alt_hi, int force_read)
+{
+ static int mv = MIN_MIC_POWER;
+ static int alt_mv = MIN_MIC_POWER;
+
+ if (mv == MIN_MIC_POWER || force_read)
+ mv = dd->meas_voltage_stable(dd);
+
+ if (mv < lo || mv > hi)
+ return 0;
+
+ if (ACCESSORY_DET_VOL_DONTCARE == alt_lo &&
+ ACCESSORY_DET_VOL_DONTCARE == alt_hi)
+ return 1;
+
+ if (alt_mv == MIN_MIC_POWER || force_read)
+ alt_mv = dd->meas_alt_voltage_stable(dd);
+
+ if (alt_mv < alt_lo || alt_mv > alt_hi)
+ return 0;
+
+ return 1;
+}
+
+/*
+ * checks whether the currently connected HW is of given type.
+ */
+static int detect_hw(struct abx500_ad *dd,
+ struct accessory_detect_task *task)
+{
+ int status;
+
+ switch (task->type) {
+ case JACK_TYPE_DISCONNECTED:
+ dd->config_hw_test_plug_connected(dd, 1);
+ status = !dd->detect_plugged_in(dd);
+ break;
+ case JACK_TYPE_CONNECTED:
+ dd->config_hw_test_plug_connected(dd, 1);
+ status = dd->detect_plugged_in(dd);
+ break;
+ case JACK_TYPE_CARKIT:
+ case JACK_TYPE_HEADPHONE:
+ case JACK_TYPE_CVIDEO:
+ case JACK_TYPE_HEADSET:
+ case JACK_TYPE_UNSUPPORTED_HEADSET:
+ case JACK_TYPE_OPENCABLE:
+ status = mic_vol_in_range(dd,
+ task->minvol,
+ task->maxvol,
+ task->alt_minvol,
+ task->alt_maxvol,
+ task->meas_mv);
+ break;
+ default:
+ status = 0;
+ }
+
+ return status;
+}
+
+/*
+ * Tries to detect the currently attached accessory
+ */
+static enum accessory_jack_type detect(struct abx500_ad *dd,
+ int *req_det_count)
+{
+ enum accessory_jack_type type = JACK_TYPE_DISCONNECTED;
+ int i;
+
+ accessory_regulator_enable(dd, REGULATOR_VAUDIO | REGULATOR_AVSWITCH);
+ /* enable the VAMIC1 regulator */
+ dd->config_hw_test_basic_carkit(dd, 0);
+
+ for (i = 0; i < ARRAY_SIZE(detect_ops); ++i) {
+ if (detect_hw(dd, &detect_ops[i])) {
+ type = detect_ops[i].type;
+ *req_det_count = detect_ops[i].req_det_count;
+ break;
+ }
+ }
+
+ dd->config_hw_test_plug_connected(dd, 0);
+
+ if (jack_supports_buttons(type))
+ accessory_regulator_enable(dd, REGULATOR_VAMIC1);
+ else
+ accessory_regulator_disable(dd, REGULATOR_VAMIC1 |
+ REGULATOR_AVSWITCH);
+
+ accessory_regulator_disable(dd, REGULATOR_VAUDIO);
+
+ return type;
+}
+
+/*
+ * registers to specific interrupt
+ */
+static void claim_irq(struct abx500_ad *dd, enum accessory_irq irq_id)
+{
+ int ret;
+ int irq;
+
+ if (dd->pdata->is_detection_inverted)
+ abx500_accdet_irq_desc = dd->irq_desc_inverted;
+ else
+ abx500_accdet_irq_desc = dd->irq_desc_norm;
+
+ if (abx500_accdet_irq_desc[irq_id].registered)
+ return;
+
+ irq = platform_get_irq_byname(
+ dd->pdev,
+ abx500_accdet_irq_desc[irq_id].name);
+ if (irq < 0) {
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to get irq %s\n", __func__,
+ abx500_accdet_irq_desc[irq_id].name);
+ return;
+ }
+
+ ret = request_threaded_irq(irq,
+ NULL,
+ abx500_accdet_irq_desc[irq_id].isr,
+ IRQF_NO_SUSPEND | IRQF_SHARED,
+ abx500_accdet_irq_desc[irq_id].name,
+ dd);
+ if (ret != 0) {
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to claim irq %s (%d)\n",
+ __func__,
+ abx500_accdet_irq_desc[irq_id].name,
+ ret);
+ } else {
+ abx500_accdet_irq_desc[irq_id].registered = 1;
+ dev_dbg(&dd->pdev->dev, "%s: %s\n",
+ __func__, abx500_accdet_irq_desc[irq_id].name);
+ }
+}
+
+/*
+ * releases specific interrupt
+ */
+static void release_irq(struct abx500_ad *dd, enum accessory_irq irq_id)
+{
+ int irq;
+
+ if (dd->pdata->is_detection_inverted)
+ abx500_accdet_irq_desc = dd->irq_desc_inverted;
+ else
+ abx500_accdet_irq_desc = dd->irq_desc_norm;
+
+ if (!abx500_accdet_irq_desc[irq_id].registered)
+ return;
+
+ irq = platform_get_irq_byname(
+ dd->pdev,
+ abx500_accdet_irq_desc[irq_id].name);
+ if (irq < 0) {
+ dev_err(&dd->pdev->dev,
+ "%s: Failed to get irq %s (%d)\n",
+ __func__,
+ abx500_accdet_irq_desc[irq_id].name, irq);
+ } else {
+ free_irq(irq, dd);
+ abx500_accdet_irq_desc[irq_id].registered = 0;
+ dev_dbg(&dd->pdev->dev, "%s: %s\n",
+ __func__, abx500_accdet_irq_desc[irq_id].name);
+ }
+}
+
+/*
+ * configures interrupts + detection hardware to meet the requirements
+ * set by currently attached accessory type.
+ */
+static void config_accdetect(struct abx500_ad *dd)
+{
+ switch (dd->jack_type) {
+ case JACK_TYPE_UNSPECIFIED:
+ dd->config_accdetect1_hw(dd, 1);
+ dd->config_accdetect2_hw(dd, 0);
+
+ release_irq(dd, PLUG_IRQ);
+ release_irq(dd, UNPLUG_IRQ);
+ release_irq(dd, BUTTON_PRESS_IRQ);
+ release_irq(dd, BUTTON_RELEASE_IRQ);
+ if (dd->set_av_switch)
+ dd->set_av_switch(dd, NOT_SET);
+ break;
+
+ case JACK_TYPE_DISCONNECTED:
+ if (dd->set_av_switch)
+ dd->set_av_switch(dd, NOT_SET);
+ case JACK_TYPE_HEADPHONE:
+ case JACK_TYPE_CVIDEO:
+ dd->config_accdetect1_hw(dd, 1);
+ dd->config_accdetect2_hw(dd, 0);
+
+ claim_irq(dd, PLUG_IRQ);
+ claim_irq(dd, UNPLUG_IRQ);
+ release_irq(dd, BUTTON_PRESS_IRQ);
+ release_irq(dd, BUTTON_RELEASE_IRQ);
+ break;
+
+ case JACK_TYPE_UNSUPPORTED_HEADSET:
+ dd->config_accdetect1_hw(dd, 1);
+ dd->config_accdetect2_hw(dd, 1);
+
+ release_irq(dd, PLUG_IRQ);
+ claim_irq(dd, UNPLUG_IRQ);
+ release_irq(dd, BUTTON_PRESS_IRQ);
+ release_irq(dd, BUTTON_RELEASE_IRQ);
+ if (dd->set_av_switch)
+ dd->set_av_switch(dd, NOT_SET);
+ break;
+
+ case JACK_TYPE_CONNECTED:
+ case JACK_TYPE_HEADSET:
+ case JACK_TYPE_CARKIT:
+ case JACK_TYPE_OPENCABLE:
+ dd->config_accdetect1_hw(dd, 1);
+ dd->config_accdetect2_hw(dd, 1);
+
+ release_irq(dd, PLUG_IRQ);
+ claim_irq(dd, UNPLUG_IRQ);
+ claim_irq(dd, BUTTON_PRESS_IRQ);
+ claim_irq(dd, BUTTON_RELEASE_IRQ);
+ break;
+
+ default:
+ dev_err(&dd->pdev->dev, "%s: Unknown type: %d\n",
+ __func__, dd->jack_type);
+ }
+}
+
+/*
+ * Deferred initialization of the work.
+ */
+static void init_work(struct work_struct *work)
+{
+ struct abx500_ad *dd = container_of(work,
+ struct abx500_ad, init_work.work);
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__);
+
+ dd->jack_type = dd->reported_jack_type = JACK_TYPE_UNSPECIFIED;
+ config_accdetect(dd);
+ queue_delayed_work(dd->irq_work_queue,
+ &dd->detect_work,
+ msecs_to_jiffies(0));
+}
+
+/*
+ * performs platform device initialization
+ */
+static int abx500_accessory_init(struct platform_device *pdev)
+{
+ int ret;
+ struct abx500_ad *dd = (struct abx500_ad *)pdev->id_entry->driver_data;
+
+ dev_dbg(&pdev->dev, "Enter: %s\n", __func__);
+
+ dd->pdev = pdev;
+ dd->pdata = dd->get_platform_data(pdev);
+ if (IS_ERR(dd->pdata))
+ return PTR_ERR(dd->pdata);
+
+ if (dd->pdata->video_ctrl_gpio) {
+ ret = gpio_is_valid(dd->pdata->video_ctrl_gpio);
+ if (!ret) {
+ dev_err(&pdev->dev,
+ "%s: Video ctrl GPIO invalid (%d).\n", __func__,
+ dd->pdata->video_ctrl_gpio);
+
+ return ret;
+ }
+ ret = gpio_request(dd->pdata->video_ctrl_gpio,
+ "Video Control");
+ if (ret) {
+ dev_err(&pdev->dev, "%s: Get video ctrl GPIO"
+ "failed.\n", __func__);
+ return ret;
+ }
+ }
+
+ (ret = create_btn_input_dev(dd));
+ if (ret < 0) {
+ dev_err(&pdev->dev, "%s: create_button_input_dev failed.\n",
+ __func__);
+ goto fail_no_btn_input_dev;
+ }
+
+ ret = create_regulators(dd);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "%s: failed to create regulators\n",
+ __func__);
+ goto fail_no_regulators;
+ }
+ dd->btn_state = BUTTON_UNK;
+
+ dd->irq_work_queue = create_singlethread_workqueue("abx500_accdet_wq");
+ if (!dd->irq_work_queue) {
+ dev_err(&pdev->dev, "%s: Failed to create wq\n", __func__);
+ ret = -ENOMEM;
+ goto fail_no_mem_for_wq;
+ }
+
+ dd->gpadc = dd->accdet_abx500_gpadc_get();
+
+ INIT_DELAYED_WORK(&dd->detect_work, detect_work);
+ INIT_DELAYED_WORK(&dd->unplug_irq_work, unplug_irq_handler_work);
+ INIT_DELAYED_WORK(&dd->init_work, init_work);
+
+ /* Deferred init/detect since no use for the info early in boot */
+ queue_delayed_work(dd->irq_work_queue,
+ &dd->init_work,
+ msecs_to_jiffies(INIT_DELAY_MS));
+
+ platform_set_drvdata(pdev, dd);
+
+ return 0;
+fail_no_mem_for_wq:
+ free_regulators(dd);
+fail_no_regulators:
+ input_unregister_device(dd->btn_input_dev);
+fail_no_btn_input_dev:
+ if (dd->pdata->video_ctrl_gpio)
+ gpio_free(dd->pdata->video_ctrl_gpio);
+ return ret;
+}
+
+/*
+ * Performs platform device cleanup
+ */
+static void abx500_accessory_cleanup(struct abx500_ad *dd)
+{
+ dev_dbg(&dd->pdev->dev, "Enter: %s\n", __func__);
+
+ dd->jack_type = JACK_TYPE_UNSPECIFIED;
+ config_accdetect(dd);
+
+ gpio_free(dd->pdata->video_ctrl_gpio);
+ input_unregister_device(dd->btn_input_dev);
+ free_regulators(dd);
+
+ cancel_delayed_work(&dd->detect_work);
+ cancel_delayed_work(&dd->unplug_irq_work);
+ cancel_delayed_work(&dd->init_work);
+ flush_workqueue(dd->irq_work_queue);
+ destroy_workqueue(dd->irq_work_queue);
+
+ kfree(dd);
+}
+
+static int __devinit abx500_acc_detect_probe(struct platform_device *pdev)
+{
+
+ return abx500_accessory_init(pdev);
+}
+
+static int __devexit abx500_acc_detect_remove(struct platform_device *pdev)
+{
+ abx500_accessory_cleanup(platform_get_drvdata(pdev));
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#if defined(CONFIG_PM)
+static int abx500_acc_detect_suspend(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev,
+ struct platform_device, dev);
+ struct abx500_ad *dd = platform_get_drvdata(pdev);
+ int irq_id, irq;
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__);
+
+ cancel_delayed_work_sync(&dd->unplug_irq_work);
+ cancel_delayed_work_sync(&dd->detect_work);
+ cancel_delayed_work_sync(&dd->init_work);
+
+ if (dd->pdata->is_detection_inverted)
+ abx500_accdet_irq_desc = dd->irq_desc_inverted;
+ else
+ abx500_accdet_irq_desc = dd->irq_desc_norm;
+
+ for (irq_id = 0; irq_id < dd->no_irqs; irq_id++) {
+ if (abx500_accdet_irq_desc[irq_id].registered == 1) {
+ irq = platform_get_irq_byname(
+ dd->pdev,
+ abx500_accdet_irq_desc[irq_id].name);
+
+ disable_irq(irq);
+ }
+ }
+
+ dd->turn_off_accdet_comparator(pdev);
+
+ if (dd->jack_type == JACK_TYPE_HEADSET)
+ accessory_regulator_disable(dd, REGULATOR_VAMIC1);
+
+ return 0;
+}
+
+static int abx500_acc_detect_resume(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev,
+ struct platform_device, dev);
+ struct abx500_ad *dd = platform_get_drvdata(pdev);
+ int irq_id, irq;
+
+ dev_dbg(&dd->pdev->dev, "%s: Enter\n", __func__);
+
+ if (dd->jack_type == JACK_TYPE_HEADSET)
+ accessory_regulator_enable(dd, REGULATOR_VAMIC1);
+
+ dd->turn_on_accdet_comparator(pdev);
+
+ if (dd->pdata->is_detection_inverted)
+ abx500_accdet_irq_desc = dd->irq_desc_inverted;
+ else
+ abx500_accdet_irq_desc = dd->irq_desc_norm;
+
+ for (irq_id = 0; irq_id < dd->no_irqs; irq_id++) {
+ if (abx500_accdet_irq_desc[irq_id].registered == 1) {
+ irq = platform_get_irq_byname(
+ dd->pdev,
+ abx500_accdet_irq_desc[irq_id].name);
+
+ enable_irq(irq);
+
+ }
+ }
+
+ /* After resume, reinitialize */
+ dd->gpio35_dir_set = dd->accdet1_th_set = dd->accdet2_th_set = 0;
+ queue_delayed_work(dd->irq_work_queue, &dd->init_work, 0);
+
+ return 0;
+}
+#else
+#define abx500_acc_detect_suspend NULL
+#define abx500_acc_detect_resume NULL
+#endif
+
+static struct platform_device_id abx500_accdet_ids[] = {
+#ifdef CONFIG_INPUT_AB5500_ACCDET
+ { "ab5500-acc-det", (kernel_ulong_t)&ab5500_accessory_det_callbacks, },
+#endif
+#ifdef CONFIG_INPUT_AB8500_ACCDET
+ { "ab8500-acc-det", (kernel_ulong_t)&ab8500_accessory_det_callbacks, },
+#endif
+ { },
+};
+
+static const struct dev_pm_ops abx_ops = {
+ .suspend = abx500_acc_detect_suspend,
+ .resume = abx500_acc_detect_resume,
+};
+
+static struct platform_driver abx500_acc_detect_platform_driver = {
+ .driver = {
+ .name = "abx500-acc-det",
+ .owner = THIS_MODULE,
+ .pm = &abx_ops,
+ },
+ .probe = abx500_acc_detect_probe,
+ .id_table = abx500_accdet_ids,
+ .remove = __devexit_p(abx500_acc_detect_remove),
+};
+
+static int __init abx500_acc_detect_init(void)
+{
+ return platform_driver_register(&abx500_acc_detect_platform_driver);
+}
+
+static void __exit abx500_acc_detect_exit(void)
+{
+ platform_driver_unregister(&abx500_acc_detect_platform_driver);
+}
+
+module_init(abx500_acc_detect_init);
+module_exit(abx500_acc_detect_exit);
+
+MODULE_DESCRIPTION("ABx500 AV Accessory detection driver");
+MODULE_ALIAS("platform:abx500-acc-det");
+MODULE_AUTHOR("ST-Ericsson");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/input/misc/lps001wp_prs.c b/drivers/input/misc/lps001wp_prs.c
new file mode 100644
index 00000000000..45a322729ff
--- /dev/null
+++ b/drivers/input/misc/lps001wp_prs.c
@@ -0,0 +1,1453 @@
+
+/******************** (C) COPYRIGHT 2010 STMicroelectronics ********************
+*
+* File Name : lps001wp_prs.c
+* Authors : MSH - Motion Mems BU - Application Team
+* : Matteo Dameno (matteo.dameno@st.com)
+* : Carmine Iascone (carmine.iascone@st.com)
+* : Both authors are willing to be considered the contact
+* : and update points for the driver.
+* Version : V 1.1.1
+* Date : 2010/11/22
+* Description : LPS001WP pressure temperature sensor driver
+*
+********************************************************************************
+*
+* 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.
+*
+* THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
+* OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE
+* PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT.
+* AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT,
+* INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE
+* CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING
+* INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
+*
+******************************************************************************
+
+ Revision 0.9.0 01/10/2010:
+ first beta release
+ Revision 1.1.0 05/11/2010:
+ add sysfs management
+ Revision 1.1.1 22/11/2010:
+ moved to input/misc
+******************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/workqueue.h>
+#include <linux/device.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/input/lps001wp.h>
+#ifdef CONFIG_HAS_EARLYSUSPEND
+#include <linux/earlysuspend.h>
+#endif
+
+#define DEBUG 1
+
+#define PR_ABS_MAX 0xffff
+#define PR_ABS_MIN 0x0000
+#define PR_DLT_MAX 0x7ffff
+#define PR_DLT_MIN -0x80000 /* 16-bit signed value */
+#define TEMP_MAX 0x7fff
+#define TEMP_MIN -0x80000 /* 16-bit signed value */
+
+
+#define SENSITIVITY_T_SHIFT 6 /** = 64 LSB/degrC */
+#define SENSITIVITY_P_SHIFT 4 /** = 16 LSB/mbar */
+
+
+#define OUTDATA_REG 0x28
+#define REF_PRESS_REG 0X30
+
+#define WHOAMI_LPS001WP_PRS 0xBA /* Expctd content for WAI */
+
+/* CONTROL REGISTERS */
+#define WHO_AM_I 0x0F /* WhoAmI register */
+#define CTRL_REG1 0x20 /* power / ODR control reg */
+#define CTRL_REG2 0x21 /* boot reg */
+#define CTRL_REG3 0x22 /* interrupt control reg */
+
+#define STATUS_REG 0X27 /* status reg */
+
+#define PRESS_OUT_L OUTDATA_REG
+
+
+#define REF_P_L REF_PRESS_REG /* pressure reference */
+#define REF_P_H 0x31 /* pressure reference */
+#define THS_P_L 0x32 /* pressure threshold */
+#define THS_P_H 0x33 /* pressure threshold */
+
+#define INT_CFG 0x34 /* interrupt config */
+#define INT_SRC 0x35 /* interrupt source */
+#define INT_ACK 0x36 /* interrupt acknoledge */
+/* end CONTROL REGISTRES */
+
+
+/* Barometer and Termometer output data rate ODR */
+#define LPS001WP_PRS_ODR_MASK 0x30 /* Mask to access odr bits only */
+#define LPS001WP_PRS_ODR_7_1 0x00 /* 7Hz baro and 1Hz term ODR */
+#define LPS001WP_PRS_ODR_7_7 0x10 /* 7Hz baro and 7Hz term ODR */
+#define LPS001WP_PRS_ODR_12_12 0x30 /* 12.5Hz baro and 12.5Hz term ODR */
+
+#define LPS001WP_PRS_ENABLE_MASK 0x40 /* */
+#define LPS001WP_PRS_DIFF_MASK 0x08
+#define LPS001WP_PRS_LPOW_MASK 0x80
+
+#define LPS001WP_PRS_DIFF_ON 0x08
+#define LPS001WP_PRS_DIFF_OFF 0x00
+
+#define LPS001WP_PRS_LPOW_ON 0x80
+#define LPS001WP_PRS_LPOW_OFF 0x00
+
+#define FUZZ 0
+#define FLAT 0
+#define I2C_RETRY_DELAY 5
+#define I2C_RETRIES 5
+#define I2C_AUTO_INCREMENT 0x80
+
+/* RESUME STATE INDICES */
+#define RES_CTRL_REG1 0
+#define RES_CTRL_REG2 1
+#define RES_CTRL_REG3 2
+#define RES_REF_P_L 3
+#define RES_REF_P_H 4
+#define RES_THS_P_L 5
+#define RES_THS_P_H 6
+#define RES_INT_CFG 7
+
+#define RESUME_ENTRIES 8
+/* end RESUME STATE INDICES */
+
+/* Pressure Sensor Operating Mode */
+#define LPS001WP_PRS_DIFF_ENABLE 1
+#define LPS001WP_PRS_DIFF_DISABLE 0
+#define LPS001WP_PRS_LPOWER_EN 1
+#define LPS001WP_PRS_LPOWER_DIS 0
+
+static const struct {
+ unsigned int cutoff_ms;
+ unsigned int mask;
+} lps001wp_prs_odr_table[] = {
+ {80, LPS001WP_PRS_ODR_12_12 },
+ {143, LPS001WP_PRS_ODR_7_7 },
+ {1000, LPS001WP_PRS_ODR_7_1 },
+};
+
+/**
+ * struct lps001wp_prs_data - data structure used by lps001wp_prs driver
+ * @client: i2c client
+ * @pdata: lsm303dlh platform data
+ * @lock: mutex lock for sysfs operations
+ * @input_work: work queue to read sensor data
+ * @input_dev: input device
+ * @regulator: regulator
+ * @early_suspend: early suspend structure
+ * @hw_initialized: saves hw initialisation status
+ * @hw_working: saves hw status
+ * @diff_enabled: store value of diff enable
+ * @lpowmode_enabled: flag to set lowpower mode
+ * @enabled: to store mode of device
+ * @on_before_suspend: to store status of device during suspend
+ * @resume_state:store regester values
+ * @reg_addr: stores reg address to debug
+ */
+struct lps001wp_prs_data {
+ struct i2c_client *client;
+ struct lps001wp_prs_platform_data *pdata;
+
+ struct mutex lock;
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+ struct delayed_work input_work;
+ struct input_dev *input_dev;
+#endif
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ struct early_suspend early_suspend;
+#endif
+
+ int hw_initialized;
+ /* hw_working=-1 means not tested yet */
+ int hw_working;
+ u8 diff_enabled;
+ u8 lpowmode_enabled ;
+
+ atomic_t enabled;
+ int on_before_suspend;
+
+ u8 resume_state[RESUME_ENTRIES];
+
+ struct regulator *regulator;
+
+#ifdef DEBUG
+ u8 reg_addr;
+#endif
+};
+
+struct outputdata {
+ u16 abspress;
+ s16 temperature;
+ s16 deltapress;
+};
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void lps001wp_prs_early_suspend(struct early_suspend *data);
+static void lps001wp_prs_late_resume(struct early_suspend *data);
+#endif
+
+
+static int lps001wp_prs_i2c_read(struct lps001wp_prs_data *prs,
+ u8 *buf, int len)
+{
+ int err;
+ int tries = 0;
+
+ struct i2c_msg msgs[] = {
+ {
+ .addr = prs->client->addr,
+ .flags = prs->client->flags & I2C_M_TEN,
+ .len = 1,
+ .buf = buf,
+ },
+ {
+ .addr = prs->client->addr,
+ .flags = (prs->client->flags & I2C_M_TEN) | I2C_M_RD,
+ .len = len,
+ .buf = buf,
+ },
+ };
+
+ do {
+ err = i2c_transfer(prs->client->adapter, msgs, 2);
+ if (err != 2)
+ msleep_interruptible(I2C_RETRY_DELAY);
+ } while ((err != 2) && (++tries < I2C_RETRIES));
+
+ if (err != 2) {
+ dev_err(&prs->client->dev, "read transfer error\n");
+ err = -EIO;
+ } else {
+ err = 0;
+ }
+
+ return err;
+}
+
+static int lps001wp_prs_i2c_write(struct lps001wp_prs_data *prs,
+ u8 *buf, int len)
+{
+ int err;
+ int tries = 0;
+ struct i2c_msg msgs[] = {
+ {
+ .addr = prs->client->addr,
+ .flags = prs->client->flags & I2C_M_TEN,
+ .len = len + 1,
+ .buf = buf,
+ },
+ };
+
+ do {
+ err = i2c_transfer(prs->client->adapter, msgs, 1);
+ if (err != 1)
+ msleep_interruptible(I2C_RETRY_DELAY);
+ } while ((err != 1) && (++tries < I2C_RETRIES));
+
+ if (err != 1) {
+ dev_err(&prs->client->dev, "write transfer error\n");
+ err = -EIO;
+ } else {
+ err = 0;
+ }
+
+ return err;
+}
+
+static int lps001wp_prs_register_write(struct lps001wp_prs_data *prs, u8 *buf,
+ u8 reg_address, u8 new_value)
+{
+ int err = -EINVAL;
+
+ /* Sets configuration register at reg_address
+ * NOTE: this is a straight overwrite */
+ buf[0] = reg_address;
+ buf[1] = new_value;
+ err = lps001wp_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ return err;
+ return err;
+}
+
+static int lps001wp_prs_register_read(struct lps001wp_prs_data *prs, u8 *buf,
+ u8 reg_address)
+{
+
+ int err = -EINVAL;
+ buf[0] = (reg_address);
+ err = lps001wp_prs_i2c_read(prs, buf, 1);
+
+ return err;
+}
+
+static int lps001wp_prs_register_update(struct lps001wp_prs_data *prs, u8 *buf,
+ u8 reg_address, u8 mask, u8 new_bit_values)
+{
+ int err = -EINVAL;
+ u8 init_val;
+ u8 updated_val;
+ err = lps001wp_prs_register_read(prs, buf, reg_address);
+ if (!(err < 0)) {
+ init_val = buf[0];
+ updated_val = ((mask & new_bit_values) | ((~mask) & init_val));
+ err = lps001wp_prs_register_write(prs, buf, reg_address,
+ updated_val);
+ }
+ return err;
+}
+
+/* */
+
+
+static int lps001wp_prs_hw_init(struct lps001wp_prs_data *prs)
+{
+ int err = -EINVAL;
+ u8 buf[6];
+
+ dev_dbg(&prs->client->dev, "%s: hw init start\n",
+ LPS001WP_PRS_DEV_NAME);
+
+ buf[0] = WHO_AM_I;
+ err = lps001wp_prs_i2c_read(prs, buf, 1);
+ if (err < 0)
+ goto error_firstread;
+ else
+ prs->hw_working = 1;
+ if (buf[0] != WHOAMI_LPS001WP_PRS) {
+ err = -EINVAL; /* TODO:choose the right coded error */
+ goto error_unknown_device;
+ }
+
+
+ buf[0] = (I2C_AUTO_INCREMENT | REF_PRESS_REG);
+ buf[1] = prs->resume_state[RES_REF_P_L];
+ buf[2] = prs->resume_state[RES_REF_P_H];
+ buf[3] = prs->resume_state[RES_THS_P_L];
+ buf[4] = prs->resume_state[RES_THS_P_H];
+ err = lps001wp_prs_i2c_write(prs, buf, 4);
+ if (err < 0)
+ goto error1;
+
+ buf[0] = (I2C_AUTO_INCREMENT | CTRL_REG1);
+ buf[1] = prs->resume_state[RES_CTRL_REG1];
+ buf[2] = prs->resume_state[RES_CTRL_REG2];
+ buf[3] = prs->resume_state[RES_CTRL_REG3];
+ err = lps001wp_prs_i2c_write(prs, buf, 3);
+ if (err < 0)
+ goto error1;
+
+ buf[0] = INT_CFG;
+ buf[1] = prs->resume_state[RES_INT_CFG];
+ err = lps001wp_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ goto error1;
+
+
+ prs->hw_initialized = 1;
+ dev_dbg(&prs->client->dev, "%s: hw init done\n", LPS001WP_PRS_DEV_NAME);
+ return 0;
+
+error_firstread:
+ prs->hw_working = 0;
+ dev_warn(&prs->client->dev, "Error reading WHO_AM_I: is device "
+ "available/working?\n");
+ goto error1;
+error_unknown_device:
+ dev_err(&prs->client->dev,
+ "device unknown. Expected: 0x%x,"
+ " Replies: 0x%x\n", WHOAMI_LPS001WP_PRS, buf[0]);
+error1:
+ prs->hw_initialized = 0;
+ dev_err(&prs->client->dev, "hw init error 0x%x,0x%x: %d\n", buf[0],
+ buf[1], err);
+ return err;
+}
+
+static void lps001wp_prs_device_power_off(struct lps001wp_prs_data *prs)
+{
+ int err;
+ u8 buf[2] = { CTRL_REG1, LPS001WP_PRS_PM_OFF };
+
+ err = lps001wp_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ dev_err(&prs->client->dev, "soft power off failed: %d\n", err);
+
+ /* disable regulator */
+ if (prs->regulator) {
+ err = regulator_disable(prs->regulator);
+ if (err < 0)
+ dev_err(&prs->client->dev, "failed to disable regulator\n");
+ }
+
+ if (prs->hw_initialized) {
+ prs->hw_initialized = 0;
+ }
+
+}
+
+static int lps001wp_prs_device_power_on(struct lps001wp_prs_data *prs)
+{
+ int err = -EINVAL;
+
+ /* get the regulator the first time */
+ if (!prs->regulator) {
+ prs->regulator = regulator_get(&prs->client->dev, "vdd");
+ if (IS_ERR(prs->regulator)) {
+ dev_err(&prs->client->dev, "failed to get regulator\n");
+ prs->regulator = NULL;
+ return PTR_ERR(prs->regulator);
+ }
+ }
+
+ /* enable it also */
+ err = regulator_enable(prs->regulator);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "failed to enable regulator\n");
+ return err;
+ }
+
+ if (!prs->hw_initialized) {
+ err = lps001wp_prs_hw_init(prs);
+ if (prs->hw_working == 1 && err < 0) {
+ lps001wp_prs_device_power_off(prs);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+
+
+int lps001wp_prs_update_odr(struct lps001wp_prs_data *prs, int poll_interval_ms)
+{
+ int err = -EINVAL;
+ int i;
+
+ u8 buf[2];
+ u8 updated_val;
+ u8 init_val;
+ u8 new_val;
+ u8 mask = LPS001WP_PRS_ODR_MASK;
+
+ /* Following, looks for the longest possible odr interval scrolling the
+ * odr_table vector from the end (shortest interval) backward (longest
+ * interval), to support the poll_interval requested by the system.
+ * It must be the longest interval lower then the poll interval.*/
+ for (i = ARRAY_SIZE(lps001wp_prs_odr_table) - 1; i >= 0; i--) {
+ if (lps001wp_prs_odr_table[i].cutoff_ms <= poll_interval_ms)
+ break;
+ }
+
+ new_val = lps001wp_prs_odr_table[i].mask;
+
+ /* If device is currently enabled, we need to write new
+ * configuration out to it */
+ if (atomic_read(&prs->enabled)) {
+ buf[0] = CTRL_REG1;
+ err = lps001wp_prs_i2c_read(prs, buf, 1);
+ if (err < 0)
+ goto error;
+ init_val = buf[0];
+ prs->resume_state[RES_CTRL_REG1] = init_val;
+
+ buf[0] = CTRL_REG1;
+ updated_val = ((mask & new_val) | ((~mask) & init_val));
+ buf[1] = updated_val;
+ buf[0] = CTRL_REG1;
+ err = lps001wp_prs_i2c_write(prs, buf, 1);
+ if (err < 0)
+ goto error;
+ prs->resume_state[RES_CTRL_REG1] = updated_val;
+ }
+ return err;
+
+error:
+ dev_err(&prs->client->dev, "update odr failed 0x%x,0x%x: %d\n",
+ buf[0], buf[1], err);
+
+ return err;
+}
+
+static int lps001wp_prs_set_press_reference(struct lps001wp_prs_data *prs,
+ u16 new_reference)
+{
+ int err = -EINVAL;
+ u8 const reg_addressL = REF_P_L;
+ u8 const reg_addressH = REF_P_H;
+ u8 bit_valuesL, bit_valuesH;
+ u8 buf[2];
+ /*
+ * We need to set new configurations, only if device
+ * is currently enabled
+ */
+ if (!atomic_read(&prs->enabled))
+ return err;
+ bit_valuesL = (u8) (new_reference & 0x00FF);
+ bit_valuesH = (u8)((new_reference & 0xFF00) >> 8);
+
+ err = lps001wp_prs_register_write(prs, buf, reg_addressL,
+ bit_valuesL);
+ if (err < 0)
+ return err;
+ err = lps001wp_prs_register_write(prs, buf, reg_addressH,
+ bit_valuesH);
+ if (err < 0) {
+ lps001wp_prs_register_write(prs, buf, reg_addressL,
+ prs->resume_state[RES_REF_P_L]);
+ return err;
+ }
+ prs->resume_state[RES_REF_P_L] = bit_valuesL;
+ prs->resume_state[RES_REF_P_H] = bit_valuesH;
+ return err;
+}
+
+static int lps001wp_prs_get_press_reference(struct lps001wp_prs_data *prs,
+ u16 *buf16)
+{
+ int err = -EINVAL;
+
+ u8 bit_valuesL, bit_valuesH;
+ u8 buf[2] = {0};
+ u16 temp = 0;
+ /*
+ * We need to read configurations, only if device
+ * is currently enabled
+ */
+ if (!atomic_read(&prs->enabled))
+ return err;
+ err = lps001wp_prs_register_read(prs, buf, REF_P_L);
+ if (err < 0)
+ return err;
+ bit_valuesL = buf[0];
+ err = lps001wp_prs_register_read(prs, buf, REF_P_H);
+ if (err < 0)
+ return err;
+ bit_valuesH = buf[0];
+
+ temp = (((u16) bit_valuesH) << 8);
+ *buf16 = (temp | ((u16) bit_valuesL));
+
+ return err;
+}
+
+static int lps001wp_prs_lpow_manage(struct lps001wp_prs_data *prs, u8 control)
+{
+ int err = -EINVAL;
+ u8 buf[2] = {0x00, 0x00};
+ u8 const mask = LPS001WP_PRS_LPOW_MASK;
+ u8 bit_values = LPS001WP_PRS_LPOW_OFF;
+
+ /*
+ * We need to set new configurations, only if device
+ * is currently enabled
+ */
+ if (!atomic_read(&prs->enabled))
+ return err;
+ if (control >= LPS001WP_PRS_LPOWER_EN) {
+ bit_values = LPS001WP_PRS_LPOW_ON;
+ }
+
+ err = lps001wp_prs_register_update(prs, buf, CTRL_REG1,
+ mask, bit_values);
+
+ if (err < 0)
+ return err;
+ prs->resume_state[RES_CTRL_REG1] = ((mask & bit_values) |
+ (~mask & prs->resume_state[RES_CTRL_REG1]));
+ if (bit_values == LPS001WP_PRS_LPOW_ON)
+ prs->lpowmode_enabled = 1;
+ else
+ prs->lpowmode_enabled = 0;
+ return err;
+}
+
+static int lps001wp_prs_diffen_manage(struct lps001wp_prs_data *prs, u8 control)
+{
+ int err = -EINVAL;
+ u8 buf[2] = {0x00, 0x00};
+ u8 const mask = LPS001WP_PRS_DIFF_MASK;
+ u8 bit_values = LPS001WP_PRS_DIFF_OFF;
+
+ /*
+ * We need to set new configurations, only if device
+ * is currently enabled
+ */
+ if (!atomic_read(&prs->enabled))
+ return err;
+ if (control >= LPS001WP_PRS_DIFF_ENABLE) {
+ bit_values = LPS001WP_PRS_DIFF_ON;
+ }
+
+ err = lps001wp_prs_register_update(prs, buf, CTRL_REG1,
+ mask, bit_values);
+
+ if (err < 0)
+ return err;
+ prs->resume_state[RES_CTRL_REG1] = ((mask & bit_values) |
+ (~mask & prs->resume_state[RES_CTRL_REG1]));
+ if (bit_values == LPS001WP_PRS_DIFF_ON)
+ prs->diff_enabled = 1;
+ else
+ prs->diff_enabled = 0;
+ return err;
+}
+
+
+static int lps001wp_prs_get_presstemp_data(struct lps001wp_prs_data *prs,
+ struct outputdata *out)
+{
+ int err = -EINVAL;
+ /* Data bytes from hardware PRESS_OUT_L, PRESS_OUT_H,
+ * TEMP_OUT_L, TEMP_OUT_H,
+ * DELTA_L, DELTA_H */
+ u8 prs_data[6];
+
+ u16 abspr;
+ s16 temperature, deltapr;
+ int regToRead = 4;
+ prs_data[4] = 0;
+ prs_data[5] = 0;
+
+ if (prs->diff_enabled)
+ regToRead = 6;
+
+ prs_data[0] = (I2C_AUTO_INCREMENT | OUTDATA_REG);
+ err = lps001wp_prs_i2c_read(prs, prs_data, regToRead);
+ if (err < 0)
+ return err;
+
+ abspr = ((((u16) prs_data[1] << 8) | ((u16) prs_data[0])));
+ temperature = ((s16) (((u16) prs_data[3] << 8) | ((u16)prs_data[2])));
+
+ out->abspress = (abspr >> SENSITIVITY_P_SHIFT);
+ out->temperature = (temperature >> SENSITIVITY_T_SHIFT);
+
+ deltapr = ((s16) (((u16) prs_data[5] << 8) | ((u16)prs_data[4])));
+ out->deltapress = deltapr;
+
+ return err;
+}
+
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+static void lps001wp_prs_report_values(struct lps001wp_prs_data *prs,
+ struct outputdata *out)
+{
+ input_report_abs(prs->input_dev, ABS_PR, out->abspress);
+ input_report_abs(prs->input_dev, ABS_TEMP, out->temperature);
+ input_report_abs(prs->input_dev, ABS_DLTPR, out->deltapress);
+ input_sync(prs->input_dev);
+}
+#endif
+
+static int lps001wp_prs_enable(struct lps001wp_prs_data *prs)
+{
+ int err;
+
+ if (!atomic_cmpxchg(&prs->enabled, 0, 1)) {
+ if (prs->regulator)
+ regulator_enable(prs->regulator);
+ err = lps001wp_prs_device_power_on(prs);
+ if (err < 0) {
+ atomic_set(&prs->enabled, 0);
+ return err;
+ }
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+ schedule_delayed_work(&prs->input_work,
+ msecs_to_jiffies(prs->pdata->poll_interval));
+#endif
+ }
+
+ return 0;
+}
+
+static int lps001wp_prs_disable(struct lps001wp_prs_data *prs)
+{
+ if (atomic_cmpxchg(&prs->enabled, 1, 0)) {
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+ cancel_delayed_work_sync(&prs->input_work);
+#endif
+ lps001wp_prs_device_power_off(prs);
+ if (prs->regulator)
+ regulator_disable(prs->regulator);
+ }
+
+ return 0;
+}
+
+static ssize_t attr_get_polling_rate(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int val;
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ mutex_lock(&prs->lock);
+ val = prs->pdata->poll_interval;
+ mutex_unlock(&prs->lock);
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t attr_set_polling_rate(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long interval_ms;
+ int err = -EINVAL;
+
+ if (strict_strtoul(buf, 10, &interval_ms))
+ return -EINVAL;
+ if (!interval_ms)
+ return -EINVAL;
+ mutex_lock(&prs->lock);
+ prs->pdata->poll_interval = interval_ms;
+ err = lps001wp_prs_update_odr(prs, interval_ms);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "failed to update odr %ld\n",
+ interval_ms);
+ size = err;
+ }
+ mutex_unlock(&prs->lock);
+ return size;
+}
+
+static ssize_t attr_get_diff_enable(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 val;
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ mutex_lock(&prs->lock);
+ val = prs->diff_enabled;
+ mutex_unlock(&prs->lock);
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t attr_set_diff_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long val;
+ int err = -EINVAL;
+
+ if (strict_strtoul(buf, 10, &val))
+ return -EINVAL;
+
+ mutex_lock(&prs->lock);
+ err = lps001wp_prs_diffen_manage(prs, (u8) val);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "failed to diff enable %ld\n", val);
+ mutex_unlock(&prs->lock);
+ return err;
+ }
+ mutex_unlock(&prs->lock);
+ return size;
+}
+
+static ssize_t attr_get_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ int val = atomic_read(&prs->enabled);
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t attr_set_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long val;
+
+ if (strict_strtoul(buf, 10, &val))
+ return -EINVAL;
+
+ if (val)
+ lps001wp_prs_enable(prs);
+ else
+ lps001wp_prs_disable(prs);
+
+ return size;
+}
+
+static ssize_t attr_get_press_ref(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int err = -EINVAL;
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ u16 val = 0;
+
+ mutex_lock(&prs->lock);
+ err = lps001wp_prs_get_press_reference(prs, &val);
+ mutex_unlock(&prs->lock);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "failed to get ref press\n");
+ return err;
+ }
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t attr_set_press_ref(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int err = -EINVAL;
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long val = 0;
+
+ if (strict_strtoul(buf, 10, &val))
+ return -EINVAL;
+
+ if (val < PR_ABS_MIN || val > PR_ABS_MAX)
+ return -EINVAL;
+
+ mutex_lock(&prs->lock);
+ err = lps001wp_prs_set_press_reference(prs, val);
+ mutex_unlock(&prs->lock);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "failed to set ref press %ld\n",
+ val);
+ return err;
+ }
+ return size;
+}
+
+
+static ssize_t attr_get_lowpowmode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ u8 val;
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ mutex_lock(&prs->lock);
+ val = prs->lpowmode_enabled;
+ mutex_unlock(&prs->lock);
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t attr_set_lowpowmode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int err = -EINVAL;
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long val;
+
+ if (strict_strtoul(buf, 10, &val))
+ return -EINVAL;
+
+ mutex_lock(&prs->lock);
+ err = lps001wp_prs_lpow_manage(prs, (u8) val);
+ mutex_unlock(&prs->lock);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "failed to set low powermode\n");
+ return err;
+ }
+ return size;
+}
+static ssize_t lps001wp_prs_get_press_data(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ struct outputdata out;
+ int err = -EINVAL;
+ mutex_lock(&prs->lock);
+ /*
+ * If device is currently enabled, we need to read
+ * data from it.
+ */
+ if (!atomic_read(&prs->enabled))
+ goto out;
+ err = lps001wp_prs_get_presstemp_data(prs, &out);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "get_pressure_data failed\n");
+ goto out;
+ }
+ mutex_unlock(&prs->lock);
+ return sprintf(buf, "%d", out.abspress);
+out:
+ mutex_unlock(&prs->lock);
+ return err;
+}
+
+static ssize_t lps001wp_prs_get_deltapr_data(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ struct outputdata out;
+ int err = -EINVAL;
+ mutex_lock(&prs->lock);
+ /*
+ * If device is currently enabled, we need to read
+ * data from it.
+ */
+ if (!atomic_read(&prs->enabled)) {
+ mutex_unlock(&prs->lock);
+ return err;
+ }
+ err = lps001wp_prs_get_presstemp_data(prs, &out);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "get_deltapress_data failed\n");
+ mutex_unlock(&prs->lock);
+ return err;
+ }
+ mutex_unlock(&prs->lock);
+ return sprintf(buf, "%d", out.deltapress);
+}
+
+static ssize_t lps001wp_prs_get_temp_data(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ struct outputdata out;
+ int err = -EINVAL;
+ mutex_lock(&prs->lock);
+ /*
+ * If device is currently enabled, we need to read
+ * data from it.
+ */
+ if (!atomic_read(&prs->enabled)) {
+ mutex_unlock(&prs->lock);
+ return err;
+ }
+ err = lps001wp_prs_get_presstemp_data(prs, &out);
+ if (err < 0) {
+ dev_err(&prs->client->dev, "get_temperature_data failed\n");
+ mutex_unlock(&prs->lock);
+ return err;
+ }
+ mutex_unlock(&prs->lock);
+ return sprintf(buf, "%d", out.temperature);
+}
+#ifdef DEBUG
+static ssize_t attr_reg_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int rc;
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ u8 x[2];
+ unsigned long val;
+
+ if (strict_strtoul(buf, 16, &val))
+ return -EINVAL;
+ mutex_lock(&prs->lock);
+ x[0] = prs->reg_addr;
+ mutex_unlock(&prs->lock);
+ x[1] = val;
+ rc = lps001wp_prs_i2c_write(prs, x, 1);
+ /*TODO: error need to be managed */
+ return size;
+}
+
+static ssize_t attr_reg_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ ssize_t ret;
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ int rc;
+ u8 data;
+
+ mutex_lock(&prs->lock);
+ data = prs->reg_addr;
+ mutex_unlock(&prs->lock);
+ rc = lps001wp_prs_i2c_read(prs, &data, 1);
+ /*TODO: error need to be managed */
+ ret = sprintf(buf, "0x%02x\n", data);
+ return ret;
+}
+
+static ssize_t attr_addr_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ unsigned long val;
+ if (strict_strtoul(buf, 16, &val))
+ return -EINVAL;
+ mutex_lock(&prs->lock);
+ prs->reg_addr = val;
+ mutex_unlock(&prs->lock);
+ return size;
+}
+#endif
+
+
+
+static struct device_attribute attributes[] = {
+ __ATTR(pollrate_ms, S_IWUSR | S_IRUGO, attr_get_polling_rate,
+ attr_set_polling_rate),
+ __ATTR(enable, S_IWUGO | S_IRUGO, attr_get_enable, attr_set_enable),
+ __ATTR(diff_enable, S_IWUSR | S_IRUGO, attr_get_diff_enable,
+ attr_set_diff_enable),
+ __ATTR(press_reference, S_IWUSR | S_IRUGO, attr_get_press_ref,
+ attr_set_press_ref),
+ __ATTR(lowpow_enable, S_IWUSR | S_IRUGO, attr_get_lowpowmode,
+ attr_set_lowpowmode),
+ __ATTR(press_data, S_IRUGO, lps001wp_prs_get_press_data, NULL),
+ __ATTR(temp_data, S_IRUGO, lps001wp_prs_get_temp_data, NULL),
+ __ATTR(deltapr_data, S_IRUGO, lps001wp_prs_get_deltapr_data, NULL),
+#ifdef DEBUG
+ __ATTR(reg_value, S_IWUSR | S_IRUGO, attr_reg_get, attr_reg_set),
+ __ATTR(reg_addr, S_IWUSR, NULL, attr_addr_set),
+#endif
+};
+
+static int create_sysfs_interfaces(struct device *dev)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(attributes); i++)
+ if (device_create_file(dev, attributes + i))
+ goto error;
+ return 0;
+
+error:
+ for ( ; i >= 0; i--)
+ device_remove_file(dev, attributes + i);
+ dev_err(dev, "%s:Unable to create interface\n", __func__);
+ return -1;
+}
+
+static int remove_sysfs_interfaces(struct device *dev)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(attributes); i++)
+ device_remove_file(dev, attributes + i);
+ return 0;
+}
+
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+static void lps001wp_prs_input_work_func(struct work_struct *work)
+{
+ struct lps001wp_prs_data *prs;
+
+ struct outputdata output;
+ struct outputdata *out = &output;
+ int err;
+
+ prs = container_of((struct delayed_work *)work,
+ struct lps001wp_prs_data, input_work);
+
+ mutex_lock(&prs->lock);
+ err = lps001wp_prs_get_presstemp_data(prs, out);
+ if (err < 0)
+ dev_err(&prs->client->dev, "get_pressure_data failed\n");
+ else
+ lps001wp_prs_report_values(prs, out);
+
+ schedule_delayed_work(&prs->input_work,
+ msecs_to_jiffies(prs->pdata->poll_interval));
+ mutex_unlock(&prs->lock);
+}
+
+int lps001wp_prs_input_open(struct input_dev *input)
+{
+ struct lps001wp_prs_data *prs = input_get_drvdata(input);
+
+ return lps001wp_prs_enable(prs);
+}
+
+void lps001wp_prs_input_close(struct input_dev *dev)
+{
+ struct lps001wp_prs_data *prs = input_get_drvdata(dev);
+
+ lps001wp_prs_disable(prs);
+}
+#endif
+
+static int lps001wp_prs_validate_pdata(struct lps001wp_prs_data *prs)
+{
+ prs->pdata->poll_interval = max(prs->pdata->poll_interval,
+ prs->pdata->min_interval);
+
+ /* Enforce minimum polling interval */
+ if (prs->pdata->poll_interval < prs->pdata->min_interval) {
+ dev_err(&prs->client->dev, "minimum poll interval violated\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+static int lps001wp_prs_input_init(struct lps001wp_prs_data *prs)
+{
+ int err;
+ INIT_DELAYED_WORK(&prs->input_work, lps001wp_prs_input_work_func);
+ prs->input_dev = input_allocate_device();
+ if (!prs->input_dev) {
+ err = -ENOMEM;
+ dev_err(&prs->client->dev, "input device allocate failed\n");
+ goto err0;
+ }
+
+ prs->input_dev->open = lps001wp_prs_input_open;
+ prs->input_dev->close = lps001wp_prs_input_close;
+ prs->input_dev->name = LPS001WP_PRS_DEV_NAME;
+ prs->input_dev->id.bustype = BUS_I2C;
+ prs->input_dev->dev.parent = &prs->client->dev;
+
+ input_set_drvdata(prs->input_dev, prs);
+
+ set_bit(EV_ABS, prs->input_dev->evbit);
+
+ input_set_abs_params(prs->input_dev, ABS_PR,
+ PR_ABS_MIN, PR_ABS_MAX, FUZZ, FLAT);
+ input_set_abs_params(prs->input_dev, ABS_TEMP,
+ PR_DLT_MIN, PR_DLT_MAX, FUZZ, FLAT);
+ input_set_abs_params(prs->input_dev, ABS_DLTPR,
+ TEMP_MIN, TEMP_MAX, FUZZ, FLAT);
+
+
+ prs->input_dev->name = "LPS001WP barometer";
+
+ err = input_register_device(prs->input_dev);
+ if (err) {
+ dev_err(&prs->client->dev,
+ "unable to register input polled device %s\n",
+ prs->input_dev->name);
+ goto err1;
+ }
+
+ return 0;
+
+err1:
+ input_free_device(prs->input_dev);
+err0:
+ return err;
+}
+
+static void lps001wp_prs_input_cleanup(struct lps001wp_prs_data *prs)
+{
+ input_unregister_device(prs->input_dev);
+ input_free_device(prs->input_dev);
+}
+#endif
+static int lps001wp_prs_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lps001wp_prs_data *prs;
+ int err = -EINVAL;
+ int tempvalue;
+
+ pr_info("%s: probe start.\n", LPS001WP_PRS_DEV_NAME);
+
+ if (client->dev.platform_data == NULL) {
+ dev_err(&client->dev, "platform data is NULL. exiting.\n");
+ err = -ENODEV;
+ goto exit_check_functionality_failed;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "client not i2c capable\n");
+ err = -ENODEV;
+ goto exit_check_functionality_failed;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WORD_DATA)) {
+ dev_err(&client->dev, "client not smb-i2c capable:2\n");
+ err = -EIO;
+ goto exit_check_functionality_failed;
+ }
+
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_I2C_BLOCK)){
+ dev_err(&client->dev, "client not smb-i2c capable:3\n");
+ err = -EIO;
+ goto exit_check_functionality_failed;
+ }
+
+
+ prs = kzalloc(sizeof(struct lps001wp_prs_data), GFP_KERNEL);
+ if (prs == NULL) {
+ err = -ENOMEM;
+ dev_err(&client->dev,
+ "failed to allocate memory for module data: "
+ "%d\n", err);
+ goto exit_alloc_data_failed;
+ }
+
+ mutex_init(&prs->lock);
+ mutex_lock(&prs->lock);
+
+ prs->client = client;
+ i2c_set_clientdata(client, prs);
+
+ prs->regulator = regulator_get(&client->dev, "vdd");
+ if (IS_ERR(prs->regulator)) {
+ dev_err(&client->dev, "failed to get regulator\n");
+ err = PTR_ERR(prs->regulator);
+ prs->regulator = NULL;
+ }
+ if (prs->regulator)
+ regulator_enable(prs->regulator);
+
+ if (i2c_smbus_read_byte(client) < 0) {
+ dev_err(&client->dev, "i2c_smbus_read_byte error!!\n");
+ goto err_mutexunlockfreedata;
+ } else {
+ dev_dbg(&client->dev, "%s Device detected!\n",
+ LPS001WP_PRS_DEV_NAME);
+ }
+
+ /* read chip id */
+ tempvalue = i2c_smbus_read_word_data(client, WHO_AM_I);
+ if ((tempvalue & 0x00FF) == WHOAMI_LPS001WP_PRS) {
+ dev_dbg(&client->dev, "%s I2C driver registered!\n",
+ LPS001WP_PRS_DEV_NAME);
+ } else {
+ prs->client = NULL;
+ dev_dbg(&client->dev, "I2C driver not registered!"
+ " Device unknown\n");
+ goto err_mutexunlockfreedata;
+ }
+
+ prs->pdata = kmalloc(sizeof(*prs->pdata), GFP_KERNEL);
+ if (prs->pdata == NULL) {
+ err = -ENOMEM;
+ dev_err(&client->dev,
+ "failed to allocate memory for pdata: %d\n",
+ err);
+ goto err_mutexunlockfreedata;
+ }
+
+ memcpy(prs->pdata, client->dev.platform_data, sizeof(*prs->pdata));
+
+ err = lps001wp_prs_validate_pdata(prs);
+ if (err < 0) {
+ dev_err(&client->dev, "failed to validate platform data\n");
+ goto exit_kfree_pdata;
+ }
+
+ i2c_set_clientdata(client, prs);
+
+
+ if (prs->pdata->init) {
+ err = prs->pdata->init();
+ if (err < 0) {
+ dev_err(&client->dev, "init failed: %d\n", err);
+ goto err2;
+ }
+ }
+
+ memset(prs->resume_state, 0, ARRAY_SIZE(prs->resume_state));
+
+ prs->resume_state[RES_CTRL_REG1] = LPS001WP_PRS_PM_NORMAL;
+ prs->resume_state[RES_CTRL_REG2] = 0x00;
+ prs->resume_state[RES_CTRL_REG3] = 0x00;
+ prs->resume_state[RES_REF_P_L] = 0x00;
+ prs->resume_state[RES_REF_P_H] = 0x00;
+ prs->resume_state[RES_THS_P_L] = 0x00;
+ prs->resume_state[RES_THS_P_H] = 0x00;
+ prs->resume_state[RES_INT_CFG] = 0x00;
+
+ err = lps001wp_prs_device_power_on(prs);
+ if (err < 0) {
+ dev_err(&client->dev, "power on failed: %d\n", err);
+ goto err2;
+ }
+
+ prs->diff_enabled = 0;
+ prs->lpowmode_enabled = 0;
+ atomic_set(&prs->enabled, 1);
+
+ err = lps001wp_prs_update_odr(prs, prs->pdata->poll_interval);
+ if (err < 0) {
+ dev_err(&client->dev, "update_odr failed\n");
+ goto err_power_off;
+ }
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+ err = lps001wp_prs_input_init(prs);
+ if (err < 0) {
+ dev_err(&client->dev, "input init failed\n");
+ goto err_power_off;
+ }
+#endif
+
+ err = create_sysfs_interfaces(&client->dev);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "device LPS001WP_PRS_DEV_NAME sysfs register failed\n");
+ goto err_input_cleanup;
+ }
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ prs->early_suspend.level =
+ EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+ prs->early_suspend.suspend = lps001wp_prs_early_suspend;
+ prs->early_suspend.resume = lps001wp_prs_late_resume;
+ register_early_suspend(&prs->early_suspend);
+#endif
+
+
+ lps001wp_prs_device_power_off(prs);
+
+ if (prs->regulator)
+ regulator_disable(prs->regulator);
+
+ /* As default, do not report information */
+ atomic_set(&prs->enabled, 0);
+
+
+ mutex_unlock(&prs->lock);
+
+ dev_info(&client->dev, "%s: probed\n", LPS001WP_PRS_DEV_NAME);
+
+ return 0;
+
+/*
+remove_sysfs_int:
+ remove_sysfs_interfaces(&client->dev);
+*/
+err_input_cleanup:
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+ lps001wp_prs_input_cleanup(prs);
+#endif
+err_power_off:
+ lps001wp_prs_device_power_off(prs);
+err2:
+ if (prs->pdata->exit)
+ prs->pdata->exit();
+exit_kfree_pdata:
+ kfree(prs->pdata);
+
+err_mutexunlockfreedata:
+ mutex_unlock(&prs->lock);
+ if (prs->regulator) {
+ regulator_disable(prs->regulator);
+ regulator_put(prs->regulator);
+ }
+ kfree(prs);
+exit_alloc_data_failed:
+exit_check_functionality_failed:
+ dev_err(&client->dev, "%s: Driver Init failed\n",
+ LPS001WP_PRS_DEV_NAME);
+ return err;
+}
+
+static int __devexit lps001wp_prs_remove(struct i2c_client *client)
+{
+ struct lps001wp_prs_data *prs = i2c_get_clientdata(client);
+
+#ifdef CONFIG_LPS001WP_INPUT_DEVICE
+ lps001wp_prs_input_cleanup(prs);
+#endif
+ lps001wp_prs_device_power_off(prs);
+ remove_sysfs_interfaces(&client->dev);
+ if (prs->regulator) {
+ /* Disable the regulator if device is enabled. */
+ if (atomic_read(&prs->enabled))
+ regulator_disable(prs->regulator);
+ regulator_put(prs->regulator);
+ }
+
+ if (prs->pdata->exit)
+ prs->pdata->exit();
+
+ if (prs->regulator)
+ regulator_put(prs->regulator);
+
+ kfree(prs->pdata);
+ kfree(prs);
+
+ return 0;
+}
+#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM))
+static int lps001wp_prs_resume(struct device *dev)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+
+ if (prs->on_before_suspend)
+ return lps001wp_prs_enable(prs);
+ return 0;
+}
+
+static int lps001wp_prs_suspend(struct device *dev)
+{
+ struct lps001wp_prs_data *prs = dev_get_drvdata(dev);
+ prs->on_before_suspend = atomic_read(&prs->enabled);
+ return lps001wp_prs_disable(prs);
+}
+
+static const struct dev_pm_ops lps001wp_prs_dev_pm_ops = {
+ .suspend = lps001wp_prs_suspend,
+ .resume = lps001wp_prs_resume,
+};
+#else
+static void lps001wp_prs_early_suspend(struct early_suspend *data)
+{
+ struct lps001wp_prs_data *prs =
+ container_of(data, struct lps001wp_prs_data, early_suspend);
+ prs->on_before_suspend = atomic_read(&prs->enabled);
+ lps001wp_prs_disable(prs);
+}
+
+static void lps001wp_prs_late_resume(struct early_suspend *data)
+{
+ struct lps001wp_prs_data *prs =
+ container_of(data, struct lps001wp_prs_data, early_suspend);
+ if (prs->on_before_suspend)
+ lps001wp_prs_enable(prs);
+}
+#endif
+static const struct i2c_device_id lps001wp_prs_id[]
+ = { { LPS001WP_PRS_DEV_NAME, 0}, { },};
+
+MODULE_DEVICE_TABLE(i2c, lps001wp_prs_id);
+
+static struct i2c_driver lps001wp_prs_driver = {
+ .driver = {
+ .name = LPS001WP_PRS_DEV_NAME,
+ .owner = THIS_MODULE,
+#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM))
+ .pm = &lps001wp_prs_dev_pm_ops,
+#endif
+ },
+ .probe = lps001wp_prs_probe,
+ .remove = __devexit_p(lps001wp_prs_remove),
+ .id_table = lps001wp_prs_id,
+};
+
+static int __init lps001wp_prs_init(void)
+{
+ printk(KERN_DEBUG "%s barometer driver: init\n",
+ LPS001WP_PRS_DEV_NAME);
+ return i2c_add_driver(&lps001wp_prs_driver);
+}
+
+static void __exit lps001wp_prs_exit(void)
+{
+ #if DEBUG
+ printk(KERN_DEBUG "%s barometer driver exit\n",
+ LPS001WP_PRS_DEV_NAME);
+ #endif
+ i2c_del_driver(&lps001wp_prs_driver);
+ return;
+}
+
+module_init(lps001wp_prs_init);
+module_exit(lps001wp_prs_exit);
+
+MODULE_DESCRIPTION("STMicrolelectronics lps001wp pressure sensor sysfs driver");
+MODULE_AUTHOR("Matteo Dameno, Carmine Iascone, STMicroelectronics");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/input/misc/ste_ff_vibra.c b/drivers/input/misc/ste_ff_vibra.c
new file mode 100644
index 00000000000..9038e6be046
--- /dev/null
+++ b/drivers/input/misc/ste_ff_vibra.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ * Author: Marcin Mielczarczyk <marcin.mielczarczyk@tieto.com>
+ * for ST-Ericsson
+ * License Terms: GNU General Public License v2
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <mach/ste_audio_io_vibrator.h>
+
+#define FF_VIBRA_DOWN 0x0000 /* 0 degrees */
+#define FF_VIBRA_LEFT 0x4000 /* 90 degrees */
+#define FF_VIBRA_UP 0x8000 /* 180 degrees */
+#define FF_VIBRA_RIGHT 0xC000 /* 270 degrees */
+
+/**
+ * struct vibra_info - Vibrator information structure
+ * @idev: Pointer to input device structure
+ * @vibra_workqueue: Pointer to vibrator workqueue structure
+ * @vibra_work: Vibrator work
+ * @direction: Vibration direction
+ * @speed: Vibration speed
+ *
+ * Structure vibra_info holds vibrator informations
+ **/
+struct vibra_info {
+ struct input_dev *idev;
+ struct workqueue_struct *vibra_workqueue;
+ struct work_struct vibra_work;
+ int direction;
+ unsigned char speed;
+};
+
+/**
+ * vibra_play_work() - Vibrator work, sets speed and direction
+ * @work: Pointer to work structure
+ *
+ * This function is called from workqueue, turns on/off vibrator
+ **/
+static void vibra_play_work(struct work_struct *work)
+{
+ struct vibra_info *vinfo = container_of(work,
+ struct vibra_info, vibra_work);
+ struct ste_vibra_speed left_speed = {
+ .positive = 0,
+ .negative = 0,
+ };
+ struct ste_vibra_speed right_speed = {
+ .positive = 0,
+ .negative = 0,
+ };
+
+ /* Divide by 2 because supported range by PWM is 0-100 */
+ vinfo->speed /= 2;
+
+ if ((vinfo->direction > FF_VIBRA_DOWN) &&
+ (vinfo->direction < FF_VIBRA_UP)) {
+ /* 1 - 179 degrees, turn on left vibrator */
+ left_speed.positive = vinfo->speed;
+ } else if (vinfo->direction > FF_VIBRA_UP) {
+ /* more than 180 degrees, turn on right vibrator */
+ right_speed.positive = vinfo->speed;
+ } else {
+ /* 0 (down) or 180 (up) degrees, turn on 2 vibrators */
+ left_speed.positive = vinfo->speed;
+ right_speed.positive = vinfo->speed;
+ }
+
+ ste_audioio_vibrator_pwm_control(STE_AUDIOIO_CLIENT_FF_VIBRA,
+ left_speed, right_speed);
+}
+
+/**
+ * vibra_play() - Memless device control function
+ * @idev: Pointer to input device structure
+ * @data: Pointer to private data (not used)
+ * @effect: Pointer to force feedback effect structure
+ *
+ * This function controls memless device
+ *
+ * Returns:
+ * 0 - success
+ **/
+static int vibra_play(struct input_dev *idev, void *data,
+ struct ff_effect *effect)
+{
+ struct vibra_info *vinfo = input_get_drvdata(idev);
+
+ vinfo->direction = effect->direction;
+ vinfo->speed = effect->u.rumble.strong_magnitude >> 8;
+ if (!vinfo->speed)
+ /* Shift weak magnitude to make it feelable on vibrator */
+ vinfo->speed = effect->u.rumble.weak_magnitude >> 9;
+
+ queue_work(vinfo->vibra_workqueue, &vinfo->vibra_work);
+
+ return 0;
+}
+
+/**
+ * ste_ff_vibra_open() - Input device open function
+ * @idev: Pointer to input device structure
+ *
+ * This function is called on opening input device
+ *
+ * Returns:
+ * -ENOMEM - no memory left
+ * 0 - success
+ **/
+static int ste_ff_vibra_open(struct input_dev *idev)
+{
+ struct vibra_info *vinfo = input_get_drvdata(idev);
+
+ vinfo->vibra_workqueue =
+ create_singlethread_workqueue("ste_ff-ff-vibra");
+ if (!vinfo->vibra_workqueue) {
+ dev_err(&idev->dev, "couldn't create vibra workqueue\n");
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/**
+ * ste_ff_vibra_close() - Input device close function
+ * @idev: Pointer to input device structure
+ *
+ * This function is called on closing input device
+ **/
+static void ste_ff_vibra_close(struct input_dev *idev)
+{
+ struct vibra_info *vinfo = input_get_drvdata(idev);
+
+ cancel_work_sync(&vinfo->vibra_work);
+ INIT_WORK(&vinfo->vibra_work, vibra_play_work);
+ destroy_workqueue(vinfo->vibra_workqueue);
+ vinfo->vibra_workqueue = NULL;
+}
+
+static int __devinit ste_ff_vibra_probe(struct platform_device *pdev)
+{
+ struct vibra_info *vinfo;
+ int ret;
+
+ vinfo = kmalloc(sizeof *vinfo, GFP_KERNEL);
+ if (!vinfo) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ vinfo->idev = input_allocate_device();
+ if (!vinfo->idev) {
+ dev_err(&pdev->dev, "failed to allocate input device\n");
+ ret = -ENOMEM;
+ goto exit_vinfo_free;
+ }
+
+ vinfo->idev->name = "ste-ff-vibra";
+ vinfo->idev->dev.parent = pdev->dev.parent;
+ vinfo->idev->open = ste_ff_vibra_open;
+ vinfo->idev->close = ste_ff_vibra_close;
+ INIT_WORK(&vinfo->vibra_work, vibra_play_work);
+ __set_bit(FF_RUMBLE, vinfo->idev->ffbit);
+
+ ret = input_ff_create_memless(vinfo->idev, NULL, vibra_play);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to create memless device\n");
+ goto exit_idev_free;
+ }
+
+ ret = input_register_device(vinfo->idev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register input device\n");
+ goto exit_destroy_memless;
+ }
+
+ input_set_drvdata(vinfo->idev, vinfo);
+ platform_set_drvdata(pdev, vinfo);
+ return 0;
+
+exit_destroy_memless:
+ input_ff_destroy(vinfo->idev);
+exit_idev_free:
+ input_free_device(vinfo->idev);
+exit_vinfo_free:
+ kfree(vinfo);
+ return ret;
+}
+
+static int __devexit ste_ff_vibra_remove(struct platform_device *pdev)
+{
+ struct vibra_info *vinfo = platform_get_drvdata(pdev);
+
+ /*
+ * Function device_release() will call input_dev_release()
+ * which will free ff and input device. No need to call
+ * input_ff_destroy() and input_free_device() explicitly.
+ */
+ input_unregister_device(vinfo->idev);
+ kfree(vinfo);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver ste_ff_vibra_driver = {
+ .driver = {
+ .name = "ste_ff_vibra",
+ .owner = THIS_MODULE,
+ },
+ .probe = ste_ff_vibra_probe,
+ .remove = __devexit_p(ste_ff_vibra_remove)
+};
+
+static int __init ste_ff_vibra_init(void)
+{
+ return platform_driver_register(&ste_ff_vibra_driver);
+}
+module_init(ste_ff_vibra_init);
+
+static void __exit ste_ff_vibra_exit(void)
+{
+ platform_driver_unregister(&ste_ff_vibra_driver);
+}
+module_exit(ste_ff_vibra_exit);
+
+MODULE_AUTHOR("Marcin Mielczarczyk <marcin.mielczarczyk@tieto.com>");
+MODULE_DESCRIPTION("STE Force Feedback Vibrator Driver");
+MODULE_LICENSE("GPL v2");