diff options
Diffstat (limited to 'drivers/staging/android')
-rw-r--r-- | drivers/staging/android/Kconfig | 18 | ||||
-rw-r--r-- | drivers/staging/android/Makefile | 2 | ||||
-rw-r--r-- | drivers/staging/android/ab5500-timed-vibra.c | 490 | ||||
-rw-r--r-- | drivers/staging/android/ste_timed_vibra.c | 431 |
4 files changed, 941 insertions, 0 deletions
diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig index eb1dee26bda..a4495997d1b 100644 --- a/drivers/staging/android/Kconfig +++ b/drivers/staging/android/Kconfig @@ -8,6 +8,16 @@ config ANDROID if ANDROID +config ANDROID_AB5500_TIMED_VIBRA + bool "AB5500 Timed Output Vibrator" + depends on AB5500_CORE + depends on ANDROID_TIMED_OUTPUT + default y + help + Say Y here to enable linear/rotary vibrator driver using timed + output class device for ST-Ericsson's based on ST-Ericsson's + AB5500 Mix-Sig PMIC + config ANDROID_BINDER_IPC bool "Android Binder IPC Driver" default n @@ -53,6 +63,14 @@ config ANDROID_LOW_MEMORY_KILLER ---help--- Register processes to be killed when memory is low +config ANDROID_STE_TIMED_VIBRA + bool "ST-Ericsson Timed Output Vibrator" + depends on SND_SOC_AB8500 + depends on ANDROID_TIMED_OUTPUT + default y + help + ST-Ericsson's vibrator driver using timed output class device + source "drivers/staging/android/switch/Kconfig" config ANDROID_INTF_ALARM diff --git a/drivers/staging/android/Makefile b/drivers/staging/android/Makefile index 9b6c9ed91f6..0d642936a0b 100644 --- a/drivers/staging/android/Makefile +++ b/drivers/staging/android/Makefile @@ -1,3 +1,4 @@ +obj-$(CONFIG_ANDROID_AB5500_TIMED_VIBRA) += ab5500-timed-vibra.o obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o obj-$(CONFIG_ASHMEM) += ashmem.o obj-$(CONFIG_ANDROID_LOGGER) += logger.o @@ -6,6 +7,7 @@ obj-$(CONFIG_ANDROID_RAM_CONSOLE) += ram_console.o obj-$(CONFIG_ANDROID_TIMED_OUTPUT) += timed_output.o obj-$(CONFIG_ANDROID_TIMED_GPIO) += timed_gpio.o obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o +obj-$(CONFIG_ANDROID_STE_TIMED_VIBRA) += ste_timed_vibra.o obj-$(CONFIG_ANDROID_SWITCH) += switch/ obj-$(CONFIG_ANDROID_INTF_ALARM) += alarm.o obj-$(CONFIG_ANDROID_INTF_ALARM_DEV) += alarm-dev.o diff --git a/drivers/staging/android/ab5500-timed-vibra.c b/drivers/staging/android/ab5500-timed-vibra.c new file mode 100644 index 00000000000..35c627a6285 --- /dev/null +++ b/drivers/staging/android/ab5500-timed-vibra.c @@ -0,0 +1,490 @@ +/* + * ab5500-vibra.c - driver for vibrator in ST-Ericsson AB5500 chip + * + * Copyright (C) 2011 ST-Ericsson SA. + * + * License Terms: GNU General Public License v2 + * + * Author: Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com> + */ + +#include <linux/kernel.h> +#include <linux/hrtimer.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/err.h> +#include "timed_output.h" + +#include <linux/mfd/abx500.h> /* abx500_* */ +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/abx500/ab5500-gpadc.h> +#include <linux/ab5500-vibra.h> + +#define AB5500_VIBRA_DEV_NAME "ab5500:vibra" +#define AB5500_VIBRA_DRV_NAME "ab5500-vibrator" + +/* Vibrator Register Address Offsets */ +#define AB5500_VIB_CTRL 0x10 +#define AB5500_VIB_VOLT 0x11 +#define AB5500_VIB_FUND_FREQ 0x12 /* Linear vibra resonance freq. */ +#define AB5500_VIB_FUND_DUTY 0x13 +#define AB5500_KELVIN_ANA 0xB1 +#define AB5500_VIBRA_KELVIN 0xFE + +/* Vibrator Control */ +#define AB5500_VIB_DISABLE (0x80) +#define AB5500_VIB_PWR_ON (0x40) +#define AB5500_VIB_FUND_EN (0x20) +#define AB5500_VIB_FREQ_SHIFT (0) +#define AB5500_VIB_DUTY_SHIFT (3) +#define AB5500_VIB_VOLT_SHIFT (0) +#define AB5500_VIB_PULSE_SHIFT (4) +#define VIBRA_KELVIN_ENABLE (0x90) +#define VIBRA_KELVIN_VOUT (0x20) + +/* Vibrator Freq. (in HZ) and Duty */ +enum ab5500_vibra_freq { + AB5500_VIB_FREQ_1HZ = 1, + AB5500_VIB_FREQ_2HZ, + AB5500_VIB_FREQ_4HZ, + AB5500_VIB_FREQ_8HZ, +}; + +enum ab5500_vibra_duty { + AB5500_VIB_DUTY_100 = 0, + AB5500_VIB_DUTY_75 = 8, + AB5500_VIB_DUTY_50 = 16, + AB5500_VIB_DUTY_25 = 24, +}; + +/* Linear vibrator resonance freq. duty */ +#define AB5500_VIB_RDUTY_50 (0x7F) + +/* Vibration magnitudes */ +#define AB5500_VIB_FREQ_MAX (4) +#define AB5500_VIB_DUTY_MAX (4) + +static u8 vib_freq[AB5500_VIB_FREQ_MAX] = { + AB5500_VIB_FREQ_1HZ, + AB5500_VIB_FREQ_2HZ, + AB5500_VIB_FREQ_4HZ, + AB5500_VIB_FREQ_8HZ, +}; + +static u8 vib_duty[AB5500_VIB_DUTY_MAX] = { + AB5500_VIB_DUTY_100, + AB5500_VIB_DUTY_75, + AB5500_VIB_DUTY_50, + AB5500_VIB_DUTY_25, +}; + +/** + * struct ab5500_vibra - Vibrator driver interal info. + * @tdev: Pointer to timed output device structure + * @dev: Reference to vibra device structure + * @vibra_workqueue: Pointer to vibrator workqueue structure + * @vibra_work: Vibrator work + * @gpadc: Gpadc instance + * @vibra_wait: Vibrator wait queue head + * @vibra_lock: Vibrator lock + * @timeout_ms: Indicates how long time the vibrator will be enabled + * @timeout_start: Start of vibrator in jiffies + * @pdata: Local pointer to platform data with vibrator parameters + * @magnitude: required vibration strength + * @enable: Vibrator running status + * @eol: Vibrator end of life(eol) status + **/ +struct ab5500_vibra { + struct timed_output_dev tdev; + struct device *dev; + struct workqueue_struct *vibra_workqueue; + struct work_struct vibra_work; + struct ab5500_gpadc *gpadc; + wait_queue_head_t vibra_wait; + spinlock_t vibra_lock; + unsigned int timeout_ms; + unsigned long timeout_start; + struct ab5500_vibra_platform_data *pdata; + u8 magnitude; + bool enable; + bool eol; +}; + +static inline u8 vibra_magnitude(u8 mag) +{ + mag /= (AB5500_VIB_FREQ_MAX * AB5500_VIB_DUTY_MAX); + mag = vib_freq[mag / AB5500_VIB_FREQ_MAX] << AB5500_VIB_FREQ_SHIFT; + mag |= vib_duty[mag % AB5500_VIB_DUTY_MAX] << AB5500_VIB_DUTY_SHIFT; + + return mag; +} + +static int ab5500_setup_vibra_kelvin(struct ab5500_vibra* vibra) +{ + int ret; + + /* Establish the kelvin IP connection to be measured */ + ret = abx500_set_register_interruptible(vibra->dev, + AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + AB5500_KELVIN_ANA, VIBRA_KELVIN_ENABLE); + if (ret < 0) { + dev_err(vibra->dev, "failed to set kelvin network\n"); + return ret; + } + + /* Select vibra parameter to be measured */ + ret = abx500_set_register_interruptible(vibra->dev, AB5500_BANK_VIBRA, + AB5500_VIBRA_KELVIN, VIBRA_KELVIN_VOUT); + if (ret < 0) + dev_err(vibra->dev, "failed to select the kelvin param\n"); + + return ret; +} + +static int ab5500_vibra_start(struct ab5500_vibra* vibra) +{ + u8 ctrl = 0; + + ctrl = AB5500_VIB_PWR_ON | + vibra_magnitude(vibra->magnitude); + + if (vibra->pdata->type == AB5500_VIB_LINEAR) + ctrl |= AB5500_VIB_FUND_EN; + + return abx500_set_register_interruptible(vibra->dev, + AB5500_BANK_VIBRA, AB5500_VIB_CTRL, ctrl); +} + +static int ab5500_vibra_stop(struct ab5500_vibra* vibra) +{ + return abx500_mask_and_set_register_interruptible(vibra->dev, + AB5500_BANK_VIBRA, AB5500_VIB_CTRL, + AB5500_VIB_PWR_ON, 0); +} + +static int ab5500_vibra_eol_check(struct ab5500_vibra* vibra) +{ + int ret, vout; + + ret = ab5500_setup_vibra_kelvin(vibra); + if (ret < 0) { + dev_err(vibra->dev, "failed to setup kelvin network\n"); + return ret; + } + + /* Start vibra to measure voltage */ + ret = ab5500_vibra_start(vibra); + if (ret < 0) { + dev_err(vibra->dev, "failed to start vibra\n"); + return ret; + } + /* 20ms delay required for voltage rampup */ + wait_event_interruptible_timeout(vibra->vibra_wait, + 0, msecs_to_jiffies(20)); + + vout = ab5500_gpadc_convert(vibra->gpadc, VIBRA_KELVIN); + if (vout < 0) { + dev_err(vibra->dev, "failed to read gpadc vibra\n"); + return vout; + } + + /* Stop vibra after measuring voltage */ + ret = ab5500_vibra_stop(vibra); + if (ret < 0) { + dev_err(vibra->dev, "failed to stop vibra\n"); + return ret; + } + /* Check for vibra eol condition */ + if (vout < vibra->pdata->eol_voltage) { + vibra->eol = true; + dev_err(vibra->dev, "Vibra eol detected. Disabling vibra!\n"); + } + + return ret; +} + +/** + * ab5500_vibra_work() - Vibrator work, turns on/off vibrator + * @work: Pointer to work structure + * + * This function is called from workqueue, turns on/off vibrator + **/ +static void ab5500_vibra_work(struct work_struct *work) +{ + struct ab5500_vibra *vibra = container_of(work, + struct ab5500_vibra, vibra_work); + unsigned long flags; + int ret; + + ret = ab5500_vibra_start(vibra); + if (ret < 0) + dev_err(vibra->dev, "reg[%d] w failed: %d\n", + AB5500_VIB_CTRL, ret); + + wait_event_interruptible_timeout(vibra->vibra_wait, + 0, msecs_to_jiffies(vibra->timeout_ms)); + + ret = ab5500_vibra_stop(vibra); + if (ret < 0) + dev_err(vibra->dev, "reg[%d] w failed: %d\n", + AB5500_VIB_CTRL, ret); + + spin_lock_irqsave(&vibra->vibra_lock, flags); + + vibra->timeout_start = 0; + vibra->enable = false; + + spin_unlock_irqrestore(&vibra->vibra_lock, flags); +} + +/** + * vibra_enable() - Enables vibrator + * @tdev: Pointer to timed output device structure + * @timeout: Time indicating how long vibrator will be enabled + * + * This function enables vibrator + **/ +static void vibra_enable(struct timed_output_dev *tdev, int timeout) +{ + struct ab5500_vibra *vibra = dev_get_drvdata(tdev->dev); + unsigned long flags; + + spin_lock_irqsave(&vibra->vibra_lock, flags); + + if ((!vibra->enable || timeout) && !vibra->eol) { + vibra->enable = true; + + vibra->timeout_ms = timeout; + vibra->timeout_start = jiffies; + queue_work(vibra->vibra_workqueue, &vibra->vibra_work); + } + + spin_unlock_irqrestore(&vibra->vibra_lock, flags); +} + +/** + * vibra_get_time() - Returns remaining time to disabling vibration + * @tdev: Pointer to timed output device structure + * + * This function returns time remaining to disabling vibration + * + * Returns: + * Returns remaining time to disabling vibration + **/ +static int vibra_get_time(struct timed_output_dev *tdev) +{ + struct ab5500_vibra *vibra = dev_get_drvdata(tdev->dev); + unsigned int ms; + unsigned long flags; + + spin_lock_irqsave(&vibra->vibra_lock, flags); + + if (vibra->enable) + ms = jiffies_to_msecs(vibra->timeout_start + + msecs_to_jiffies(vibra->timeout_ms) - jiffies); + else + ms = 0; + + spin_unlock_irqrestore(&vibra->vibra_lock, flags); + + return ms; +} + +static int ab5500_vibra_reg_init(struct ab5500_vibra *vibra) +{ + int ret = 0; + u8 ctrl = 0; + u8 pulse = 0; + + ctrl = (AB5500_VIB_DUTY_50 << AB5500_VIB_DUTY_SHIFT) | + (AB5500_VIB_FREQ_8HZ << AB5500_VIB_FREQ_SHIFT); + + if (vibra->pdata->type == AB5500_VIB_LINEAR) { + ctrl |= AB5500_VIB_FUND_EN; + + if (vibra->pdata->voltage > AB5500_VIB_VOLT_MAX) + vibra->pdata->voltage = AB5500_VIB_VOLT_MAX; + + pulse = (vibra->pdata->pulse << AB5500_VIB_PULSE_SHIFT) | + (vibra->pdata->voltage << AB5500_VIB_VOLT_SHIFT); + ret = abx500_set_register_interruptible(vibra->dev, + AB5500_BANK_VIBRA, AB5500_VIB_VOLT, + pulse); + if (ret < 0) { + dev_err(vibra->dev, + "reg[%#x] w %#x failed: %d\n", + AB5500_VIB_VOLT, vibra->pdata->voltage, ret); + return ret; + } + + ret = abx500_set_register_interruptible(vibra->dev, + AB5500_BANK_VIBRA, AB5500_VIB_FUND_FREQ, + vibra->pdata->res_freq); + if (ret < 0) { + dev_err(vibra->dev, "reg[%#x] w %#x failed: %d\n", + AB5500_VIB_FUND_FREQ, + vibra->pdata->res_freq, ret); + return ret; + } + + ret = abx500_set_register_interruptible(vibra->dev, + AB5500_BANK_VIBRA, AB5500_VIB_FUND_DUTY, + AB5500_VIB_RDUTY_50); + if (ret < 0) { + dev_err(vibra->dev, "reg[%#x] w %#x failed: %d\n", + AB5500_VIB_FUND_DUTY, + AB5500_VIB_RDUTY_50, ret); + return ret; + } + } + + ret = abx500_set_register_interruptible(vibra->dev, + AB5500_BANK_VIBRA, AB5500_VIB_CTRL, ctrl); + if (ret < 0) { + dev_err(vibra->dev, "reg[%#x] w %#x failed: %d\n", + AB5500_VIB_CTRL, ctrl, ret); + return ret; + } + + return ret; +} + +static int ab5500_vibra_register_dev(struct ab5500_vibra *vibra, + struct platform_device *pdev) +{ + int ret = 0; + + ret = timed_output_dev_register(&vibra->tdev); + if (ret) { + dev_err(&pdev->dev, "failed to register timed output device\n"); + goto err_out; + } + + dev_set_drvdata(vibra->tdev.dev, vibra); + + + /* Create workqueue just for timed output vibrator */ + vibra->vibra_workqueue = + create_singlethread_workqueue("ste-timed-output-vibra"); + if (!vibra->vibra_workqueue) { + dev_err(&pdev->dev, "failed to allocate workqueue\n"); + ret = -ENOMEM; + goto exit_output_unregister; + } + + init_waitqueue_head(&vibra->vibra_wait); + INIT_WORK(&vibra->vibra_work, ab5500_vibra_work); + spin_lock_init(&vibra->vibra_lock); + + platform_set_drvdata(pdev, vibra); + + return ret; + +exit_output_unregister: + timed_output_dev_unregister(&vibra->tdev); +err_out: + return ret; +} + +static int __devinit ab5500_vibra_probe(struct platform_device *pdev) +{ + struct ab5500_vibra_platform_data *pdata = pdev->dev.platform_data; + struct ab5500_vibra *vibra = NULL; + int ret = 0; + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data required. Quitting...\n"); + return -ENODEV; + } + + vibra = kzalloc(sizeof(struct ab5500_vibra), GFP_KERNEL); + if (vibra == NULL) + return -ENOMEM; + + vibra->tdev.name = "vibrator"; + vibra->tdev.enable = vibra_enable; + vibra->tdev.get_time = vibra_get_time; + vibra->timeout_start = 0; + vibra->enable = false; + vibra->magnitude = pdata->magnitude; + vibra->pdata = pdata; + vibra->dev = &pdev->dev; + + if (vibra->pdata->eol_voltage) { + vibra->gpadc = ab5500_gpadc_get("ab5500-adc.0"); + if (IS_ERR(vibra->gpadc)) + goto err_alloc; + } + + if (vibra->pdata->type == AB5500_VIB_LINEAR) + dev_info(&pdev->dev, "Linear Type Vibrators\n"); + else + dev_info(&pdev->dev, "Rotary Type Vibrators\n"); + + ret = ab5500_vibra_reg_init(vibra); + if (ret < 0) + goto err_alloc; + + ret = ab5500_vibra_register_dev(vibra, pdev); + if (ret < 0) + goto err_alloc; + + /* Perform vibra eol diagnostics if eol_voltage is set */ + if (vibra->pdata->eol_voltage) { + ret = ab5500_vibra_eol_check(vibra); + if (ret < 0) + dev_warn(&pdev->dev, "EOL check failed\n"); + } + + dev_info(&pdev->dev, "initialization success\n"); + + return ret; + +err_alloc: + kfree(vibra); + + return ret; +} + +static int __devexit ab5500_vibra_remove(struct platform_device *pdev) +{ + struct ab5500_vibra *vibra = platform_get_drvdata(pdev); + + timed_output_dev_unregister(&vibra->tdev); + destroy_workqueue(vibra->vibra_workqueue); + kfree(vibra); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver ab5500_vibra_driver = { + .probe = ab5500_vibra_probe, + .remove = __devexit_p(ab5500_vibra_remove), + .driver = { + .name = AB5500_VIBRA_DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_vibra_module_init(void) +{ + return platform_driver_register(&ab5500_vibra_driver); +} + +static void __exit ab5500_vibra_module_exit(void) +{ + platform_driver_unregister(&ab5500_vibra_driver); +} + +module_init(ab5500_vibra_module_init); +module_exit(ab5500_vibra_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com>"); +MODULE_DESCRIPTION("Timed Output Driver for AB5500 Vibrator"); + diff --git a/drivers/staging/android/ste_timed_vibra.c b/drivers/staging/android/ste_timed_vibra.c new file mode 100644 index 00000000000..4621b2fb441 --- /dev/null +++ b/drivers/staging/android/ste_timed_vibra.c @@ -0,0 +1,431 @@ +/* + * 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/hrtimer.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/ste_timed_vibra.h> +#include <linux/delay.h> +#include "timed_output.h" + +/** + * struct vibra_info - Vibrator information structure + * @tdev: Pointer to timed output device structure + * @linear_workqueue: Pointer to linear vibrator workqueue structure + * @linear_work: Linear Vibrator work + * @linear_tick: Linear Vibrator high resolution timer + * @vibra_workqueue: Pointer to vibrator workqueue structure + * @vibra_work: Vibrator work + * @vibra_timer: Vibrator high resolution timer + * @vibra_lock: Vibrator lock + * @vibra_state: Actual vibrator state + * @state_force: Indicates if oppositive state is requested + * @timeout: Indicates how long time the vibrator will be enabled + * @time_passed: Total time passed in states + * @pdata: Local pointer to platform data with vibrator parameters + * + * Structure vibra_info holds vibrator information + **/ +struct vibra_info { + struct timed_output_dev tdev; + struct workqueue_struct *linear_workqueue; + struct work_struct linear_work; + struct hrtimer linear_tick; + struct workqueue_struct *vibra_workqueue; + struct work_struct vibra_work; + struct hrtimer vibra_timer; + spinlock_t vibra_lock; + enum ste_timed_vibra_states vibra_state; + bool state_force; + unsigned int timeout; + unsigned int time_passed; + struct ste_timed_vibra_platform_data *pdata; +}; + +/* + * Linear vibrator hardware operates on a particular resonance + * frequency. The resonance frequency (f) may also vary with h/w. + * This define is half time period (t) in micro seconds (us). + * For resonance frequency f = 150 Hz + * t = T/2 = ((1/150) / 2) = 3333 usec. + */ +#define LINEAR_RESONANCE 3333 + +/** + * linear_vibra_work() - Linear Vibrator work, turns on/off vibrator + * @work: Pointer to work structure + * + * This function is called from workqueue, turns on/off vibrator + **/ +static void linear_vibra_work(struct work_struct *work) +{ + struct vibra_info *vinfo = + container_of(work, struct vibra_info, linear_work); + unsigned char speed_pos = 0, speed_neg = 0; + ktime_t ktime; + static unsigned char toggle; + + if (toggle) { + speed_pos = vinfo->pdata->boost_level; + speed_neg = 0; + } else { + speed_neg = vinfo->pdata->boost_level; + speed_pos = 0; + } + + toggle = !toggle; + vinfo->pdata->timed_vibra_control(speed_pos, speed_neg, + speed_pos, speed_neg); + + if ((vinfo->vibra_state != STE_VIBRA_IDLE) && + (vinfo->vibra_state != STE_VIBRA_OFF)) { + ktime = ktime_set((LINEAR_RESONANCE / USEC_PER_SEC), + (LINEAR_RESONANCE % USEC_PER_SEC) * NSEC_PER_USEC); + hrtimer_start(&vinfo->linear_tick, ktime, HRTIMER_MODE_REL); + } +} + +/** + * vibra_control_work() - Vibrator work, turns on/off vibrator + * @work: Pointer to work structure + * + * This function is called from workqueue, turns on/off vibrator + **/ +static void vibra_control_work(struct work_struct *work) +{ + struct vibra_info *vinfo = + container_of(work, struct vibra_info, vibra_work); + unsigned val = 0; + unsigned char speed_pos = 0, speed_neg = 0; + unsigned long flags; + + /* + * Cancel scheduled timer if it has not started + * else it will wait for timer callback to complete. + * It should be done before taking vibra_lock to + * prevent race condition, as timer callback also + * takes same lock. + */ + hrtimer_cancel(&vinfo->vibra_timer); + + spin_lock_irqsave(&vinfo->vibra_lock, flags); + + switch (vinfo->vibra_state) { + case STE_VIBRA_BOOST: + /* Turn on both vibrators with boost speed */ + speed_pos = vinfo->pdata->boost_level; + val = vinfo->pdata->boost_time; + break; + case STE_VIBRA_ON: + /* Turn on both vibrators with speed */ + speed_pos = vinfo->pdata->on_level; + val = vinfo->timeout - vinfo->pdata->boost_time; + break; + case STE_VIBRA_OFF: + /* Turn on both vibrators with reversed speed */ + speed_neg = vinfo->pdata->off_level; + val = vinfo->pdata->off_time; + break; + case STE_VIBRA_IDLE: + vinfo->time_passed = 0; + break; + default: + break; + } + spin_unlock_irqrestore(&vinfo->vibra_lock, flags); + + /* Send new settings (only for rotary vibrators) */ + if (!vinfo->pdata->is_linear_vibra) + vinfo->pdata->timed_vibra_control(speed_pos, speed_neg, + speed_pos, speed_neg); + + if (vinfo->vibra_state != STE_VIBRA_IDLE) { + /* Start timer if it's not in IDLE state */ + ktime_t ktime; + ktime = ktime_set((val / MSEC_PER_SEC), + (val % MSEC_PER_SEC) * NSEC_PER_MSEC), + hrtimer_start(&vinfo->vibra_timer, ktime, HRTIMER_MODE_REL); + } else if (vinfo->pdata->is_linear_vibra) { + /* Cancel work and timers of linear vibrator in IDLE state */ + hrtimer_cancel(&vinfo->linear_tick); + flush_workqueue(vinfo->linear_workqueue); + vinfo->pdata->timed_vibra_control(0, 0, 0, 0); + } +} + +/** + * vibra_enable() - Enables vibrator + * @tdev: Pointer to timed output device structure + * @timeout: Time indicating how long vibrator will be enabled + * + * This function enables vibrator + **/ +static void vibra_enable(struct timed_output_dev *tdev, int timeout) +{ + struct vibra_info *vinfo = dev_get_drvdata(tdev->dev); + unsigned long flags; + + spin_lock_irqsave(&vinfo->vibra_lock, flags); + switch (vinfo->vibra_state) { + case STE_VIBRA_IDLE: + if (timeout) + vinfo->vibra_state = STE_VIBRA_BOOST; + else /* Already disabled */ + break; + + vinfo->state_force = false; + /* Trim timeout */ + vinfo->timeout = timeout < vinfo->pdata->boost_time ? + vinfo->pdata->boost_time : timeout; + + if (vinfo->pdata->is_linear_vibra) + queue_work(vinfo->linear_workqueue, + &vinfo->linear_work); + queue_work(vinfo->vibra_workqueue, &vinfo->vibra_work); + break; + case STE_VIBRA_BOOST: + /* Force only when user requested OFF while BOOST */ + if (!timeout) + vinfo->state_force = true; + break; + case STE_VIBRA_ON: + /* If user requested OFF */ + if (!timeout) { + if (vinfo->pdata->is_linear_vibra) + hrtimer_cancel(&vinfo->linear_tick); + /* Cancel timer if it has not expired yet. + * Else setting the vibra_state to STE_VIBRA_OFF + * will make take care that vibrator will move to + * STE_VIBRA_IDLE in timer callback just after + * this function call. + */ + hrtimer_try_to_cancel(&vinfo->vibra_timer); + vinfo->vibra_state = STE_VIBRA_OFF; + queue_work(vinfo->vibra_workqueue, &vinfo->vibra_work); + } + break; + case STE_VIBRA_OFF: + /* Force only when user requested ON while OFF */ + if (timeout) + vinfo->state_force = true; + break; + default: + break; + } + spin_unlock_irqrestore(&vinfo->vibra_lock, flags); +} + +/** + * linear_vibra_tick() - Generate resonance frequency waveform + * @hrtimer: Pointer to high resolution timer structure + * + * This function helps in generating the resonance frequency + * waveform required for linear vibrators + * + * Returns: + * Returns value which indicates whether hrtimer should be restarted + **/ +static enum hrtimer_restart linear_vibra_tick(struct hrtimer *hrtimer) +{ + struct vibra_info *vinfo = + container_of(hrtimer, struct vibra_info, linear_tick); + + if ((vinfo->vibra_state != STE_VIBRA_IDLE) && + (vinfo->vibra_state != STE_VIBRA_OFF)) { + queue_work(vinfo->linear_workqueue, &vinfo->linear_work); + } + + return HRTIMER_NORESTART; +} + +/** + * vibra_timer_expired() - Handles vibrator machine state + * @hrtimer: Pointer to high resolution timer structure + * + * This function handles vibrator machine state + * + * Returns: + * Returns value which indicates wether hrtimer should be restarted + **/ +static enum hrtimer_restart vibra_timer_expired(struct hrtimer *hrtimer) +{ + struct vibra_info *vinfo = + container_of(hrtimer, struct vibra_info, vibra_timer); + unsigned long flags; + + spin_lock_irqsave(&vinfo->vibra_lock, flags); + switch (vinfo->vibra_state) { + case STE_VIBRA_BOOST: + /* If BOOST finished and force, go to OFF */ + if (vinfo->state_force) + vinfo->vibra_state = STE_VIBRA_OFF; + else + vinfo->vibra_state = STE_VIBRA_ON; + vinfo->time_passed = vinfo->pdata->boost_time; + break; + case STE_VIBRA_ON: + vinfo->vibra_state = STE_VIBRA_OFF; + vinfo->time_passed = vinfo->timeout; + break; + case STE_VIBRA_OFF: + /* If OFF finished and force, go to ON */ + if (vinfo->state_force) + vinfo->vibra_state = STE_VIBRA_ON; + else + vinfo->vibra_state = STE_VIBRA_IDLE; + vinfo->time_passed += vinfo->pdata->off_time; + break; + case STE_VIBRA_IDLE: + break; + default: + break; + } + vinfo->state_force = false; + spin_unlock_irqrestore(&vinfo->vibra_lock, flags); + + queue_work(vinfo->vibra_workqueue, &vinfo->vibra_work); + + return HRTIMER_NORESTART; +} + +/** + * vibra_get_time() - Returns remaining time to disabling vibration + * @tdev: Pointer to timed output device structure + * + * This function returns time remaining to disabling vibration + * + * Returns: + * Returns remaining time to disabling vibration + **/ +static int vibra_get_time(struct timed_output_dev *tdev) +{ + struct vibra_info *vinfo = dev_get_drvdata(tdev->dev); + u32 ms; + + if (hrtimer_active(&vinfo->vibra_timer)) { + ktime_t remain = hrtimer_get_remaining(&vinfo->vibra_timer); + ms = (u32) ktime_to_ms(remain); + return ms + vinfo->time_passed; + } else + return 0; +} + +static int __devinit ste_timed_vibra_probe(struct platform_device *pdev) +{ + int ret; + struct vibra_info *vinfo; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "No platform data supplied\n"); + return -ENODEV; + } + + vinfo = kmalloc(sizeof *vinfo, GFP_KERNEL); + if (!vinfo) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + vinfo->tdev.name = "vibrator"; + vinfo->tdev.enable = vibra_enable; + vinfo->tdev.get_time = vibra_get_time; + vinfo->time_passed = 0; + vinfo->vibra_state = STE_VIBRA_IDLE; + vinfo->state_force = false; + vinfo->pdata = pdev->dev.platform_data; + + if (vinfo->pdata->is_linear_vibra) + dev_info(&pdev->dev, "Linear Type Vibrators\n"); + else + dev_info(&pdev->dev, "Rotary Type Vibrators\n"); + + ret = timed_output_dev_register(&vinfo->tdev); + if (ret) { + dev_err(&pdev->dev, "failed to register timed output device\n"); + goto exit_free_vinfo; + } + + dev_set_drvdata(vinfo->tdev.dev, vinfo); + + vinfo->linear_workqueue = + create_singlethread_workqueue("ste-timed-linear-vibra"); + if (!vinfo->linear_workqueue) { + dev_err(&pdev->dev, "failed to allocate workqueue\n"); + ret = -ENOMEM; + goto exit_timed_output_unregister; + } + + /* Create workqueue just for timed output vibrator */ + vinfo->vibra_workqueue = + create_singlethread_workqueue("ste-timed-output-vibra"); + if (!vinfo->vibra_workqueue) { + dev_err(&pdev->dev, "failed to allocate workqueue\n"); + ret = -ENOMEM; + goto exit_destroy_workqueue; + } + + INIT_WORK(&vinfo->linear_work, linear_vibra_work); + INIT_WORK(&vinfo->vibra_work, vibra_control_work); + spin_lock_init(&vinfo->vibra_lock); + hrtimer_init(&vinfo->linear_tick, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_init(&vinfo->vibra_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + vinfo->linear_tick.function = linear_vibra_tick; + vinfo->vibra_timer.function = vibra_timer_expired; + + platform_set_drvdata(pdev, vinfo); + return 0; + +exit_destroy_workqueue: + destroy_workqueue(vinfo->linear_workqueue); +exit_timed_output_unregister: + timed_output_dev_unregister(&vinfo->tdev); +exit_free_vinfo: + kfree(vinfo); + return ret; +} + +static int __devexit ste_timed_vibra_remove(struct platform_device *pdev) +{ + struct vibra_info *vinfo = platform_get_drvdata(pdev); + + timed_output_dev_unregister(&vinfo->tdev); + destroy_workqueue(vinfo->linear_workqueue); + destroy_workqueue(vinfo->vibra_workqueue); + kfree(vinfo); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver ste_timed_vibra_driver = { + .driver = { + .name = "ste_timed_output_vibra", + .owner = THIS_MODULE, + }, + .probe = ste_timed_vibra_probe, + .remove = __devexit_p(ste_timed_vibra_remove) +}; + +static int __init ste_timed_vibra_init(void) +{ + return platform_driver_register(&ste_timed_vibra_driver); +} +module_init(ste_timed_vibra_init); + +static void __exit ste_timed_vibra_exit(void) +{ + platform_driver_unregister(&ste_timed_vibra_driver); +} +module_exit(ste_timed_vibra_exit); + +MODULE_AUTHOR("Marcin Mielczarczyk <marcin.mielczarczyk@tieto.com>"); +MODULE_DESCRIPTION("STE Timed Output Vibrator"); +MODULE_LICENSE("GPL v2"); |