diff options
Diffstat (limited to 'drivers/input/misc')
-rw-r--r-- | drivers/input/misc/Kconfig | 32 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 3 | ||||
-rw-r--r-- | drivers/input/misc/ab5500-accdet.c | 284 | ||||
-rw-r--r-- | drivers/input/misc/ab8500-accdet.c | 464 | ||||
-rw-r--r-- | drivers/input/misc/ab8500-ponkey.c | 213 | ||||
-rw-r--r-- | drivers/input/misc/abx500-accdet.c | 1019 | ||||
-rw-r--r-- | drivers/input/misc/ste_ff_vibra.c | 234 |
7 files changed, 2178 insertions, 71 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 7faf4a7fcaa..dedd5d6cf7a 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. @@ -590,4 +604,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 f55cdf4916f..e9aa2d854bc 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 @@ -55,3 +57,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..fa8e2523126 --- /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 <linux/input/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..1b96b6b3fef --- /dev/null +++ b/drivers/input/misc/ab8500-accdet.c @@ -0,0 +1,464 @@ +/* + * 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 <linux/input/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_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); + if (dd->pdata->mic_ctrl) + gpio_direction_output(dd->pdata->mic_ctrl, 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: video_ctrl pin output config failed (%d).\n", + __func__, ret); + return; + } + + if (dd->pdata->mic_ctrl) { + ret = gpio_direction_output(dd->pdata->mic_ctrl, + dir == AUDIO_IN ? 1 : 0); + if (ret < 0) { + dev_err(&dd->pdev->dev, + "%s: mic_ctrl pin output" + "config failed (%d).\n", + __func__, ret); + return; + } + } + + 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("ab8500-gpadc.0"); +} + +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..4b5017a6e60 --- /dev/null +++ b/drivers/input/misc/abx500-accdet.c @@ -0,0 +1,1019 @@ +/* + * 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 <linux/interrupt.h> +#include <linux/input/abx500-accdet.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/gpio.h> +#include <linux/mfd/abx500.h> + +#include <sound/jack.h> +#include <sound/soc.h> +#include <sound/jack.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_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_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_dbg(&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); + 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_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: + 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; + } + } + + if (dd->pdata->mic_ctrl) { + ret = gpio_is_valid(dd->pdata->mic_ctrl); + if (!ret) { + dev_err(&pdev->dev, + "%s: Mic ctrl GPIO invalid (%d).\n", __func__, + dd->pdata->mic_ctrl); + + goto mic_ctrl_fail; + } + ret = gpio_request(dd->pdata->mic_ctrl, + "Mic Control"); + if (ret) { + dev_err(&pdev->dev, "%s: Get mic ctrl GPIO" + "failed.\n", __func__); + goto mic_ctrl_fail; + } + } + + 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->mic_ctrl) + gpio_free(dd->pdata->mic_ctrl); +mic_ctrl_fail: + 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); + + if (dd->pdata->mic_ctrl) + gpio_free(dd->pdata->mic_ctrl); + + if (dd->pdata->video_ctrl_gpio) + 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); + +} + +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/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"); |