diff options
Diffstat (limited to 'drivers/input/misc')
-rw-r--r-- | drivers/input/misc/Kconfig | 48 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 4 | ||||
-rw-r--r-- | drivers/input/misc/ab5500-accdet.c | 284 | ||||
-rw-r--r-- | drivers/input/misc/ab8500-accdet.c | 451 | ||||
-rw-r--r-- | drivers/input/misc/ab8500-ponkey.c | 213 | ||||
-rw-r--r-- | drivers/input/misc/abx500-accdet.c | 1011 | ||||
-rw-r--r-- | drivers/input/misc/lps001wp_prs.c | 1453 | ||||
-rw-r--r-- | drivers/input/misc/ste_ff_vibra.c | 234 |
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"); |