| /* drivers/misc/sec_jack.c |
| * |
| * Copyright (C) 2010 Samsung Electronics Co.Ltd |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/workqueue.h> |
| #include <linux/irq.h> |
| #include <linux/delay.h> |
| #include <linux/types.h> |
| #include <linux/input.h> |
| #include <linux/platform_device.h> |
| #include <linux/errno.h> |
| #include <linux/err.h> |
| #include <linux/switch.h> |
| #include <linux/input.h> |
| #include <linux/timer.h> |
| #include <linux/wakelock.h> |
| #include <linux/slab.h> |
| #include <linux/gpio.h> |
| #include <linux/gpio_event.h> |
| #include <linux/sec_jack.h> |
| |
| #define MAX_ZONE_LIMIT 10 |
| #define SEND_KEY_CHECK_TIME_MS 30 /* 30ms */ |
| #define DET_CHECK_TIME_MS 200 /* 200ms */ |
| #define WAKE_LOCK_TIME (HZ * 5) /* 5 sec */ |
| |
| struct sec_jack_info { |
| struct sec_jack_platform_data *pdata; |
| struct delayed_work jack_detect_work; |
| struct work_struct buttons_work; |
| struct work_struct detect_work; |
| struct workqueue_struct *queue; |
| struct input_dev *input_dev; |
| struct wake_lock det_wake_lock; |
| struct sec_jack_zone *zone; |
| struct input_handler handler; |
| struct input_handle handle; |
| struct input_device_id ids; |
| int det_irq; |
| int dev_id; |
| int pressed; |
| int pressed_code; |
| struct platform_device *send_key_dev; |
| unsigned int cur_jack_type; |
| }; |
| |
| /* with some modifications like moving all the gpio structs inside |
| * the platform data and getting the name for the switch and |
| * gpio_event from the platform data, the driver could support more than |
| * one headset jack, but currently user space is looking only for |
| * one key file and switch for a headset so it'd be overkill and |
| * untestable so we limit to one instantiation for now. |
| */ |
| static atomic_t instantiated = ATOMIC_INIT(0); |
| |
| /* sysfs name HeadsetObserver.java looks for to track headset state |
| */ |
| struct switch_dev switch_jack_detection = { |
| .name = "h2w", |
| }; |
| |
| static struct gpio_event_direct_entry sec_jack_key_map[] = { |
| { |
| .code = KEY_UNKNOWN, |
| }, |
| }; |
| |
| static struct gpio_event_input_info sec_jack_key_info = { |
| .info.func = gpio_event_input_func, |
| .info.no_suspend = true, |
| .type = EV_KEY, |
| .debounce_time.tv64 = SEND_KEY_CHECK_TIME_MS * NSEC_PER_MSEC, |
| .keymap = sec_jack_key_map, |
| .keymap_size = ARRAY_SIZE(sec_jack_key_map) |
| }; |
| |
| static struct gpio_event_info *sec_jack_input_info[] = { |
| &sec_jack_key_info.info, |
| }; |
| |
| static struct gpio_event_platform_data sec_jack_input_data = { |
| .name = "sec_jack", |
| .info = sec_jack_input_info, |
| .info_count = ARRAY_SIZE(sec_jack_input_info), |
| }; |
| |
| /* gpio_input driver does not support to read adc value. |
| * We use input filter to support 3-buttons of headset |
| * without changing gpio_input driver. |
| */ |
| static bool sec_jack_buttons_filter(struct input_handle *handle, |
| unsigned int type, unsigned int code, |
| int value) |
| { |
| struct sec_jack_info *hi = handle->handler->private; |
| |
| if (type != EV_KEY || code != KEY_UNKNOWN) |
| return false; |
| |
| hi->pressed = value; |
| |
| /* This is called in timer handler of gpio_input driver. |
| * We use workqueue to read adc value. |
| */ |
| queue_work(hi->queue, &hi->buttons_work); |
| |
| return true; |
| } |
| |
| static int sec_jack_buttons_connect(struct input_handler *handler, |
| struct input_dev *dev, |
| const struct input_device_id *id) |
| { |
| struct sec_jack_info *hi; |
| struct sec_jack_platform_data *pdata; |
| struct sec_jack_buttons_zone *btn_zones; |
| int err; |
| int i; |
| |
| /* bind input_handler to input device related to only sec_jack */ |
| if (dev->name != sec_jack_input_data.name) |
| return -ENODEV; |
| |
| hi = handler->private; |
| pdata = hi->pdata; |
| btn_zones = pdata->buttons_zones; |
| |
| hi->input_dev = dev; |
| hi->handle.dev = dev; |
| hi->handle.handler = handler; |
| hi->handle.open = 0; |
| hi->handle.name = "sec_jack_buttons"; |
| |
| err = input_register_handle(&hi->handle); |
| if (err) { |
| pr_err("%s: Failed to register sec_jack buttons handle, " |
| "error %d\n", __func__, err); |
| goto err_register_handle; |
| } |
| |
| err = input_open_device(&hi->handle); |
| if (err) { |
| pr_err("%s: Failed to open input device, error %d\n", |
| __func__, err); |
| goto err_open_device; |
| } |
| |
| for (i = 0; i < pdata->num_buttons_zones; i++) |
| input_set_capability(dev, EV_KEY, btn_zones[i].code); |
| |
| return 0; |
| |
| err_open_device: |
| input_unregister_handle(&hi->handle); |
| err_register_handle: |
| |
| return err; |
| } |
| |
| static void sec_jack_buttons_disconnect(struct input_handle *handle) |
| { |
| input_close_device(handle); |
| input_unregister_handle(handle); |
| } |
| |
| static void sec_jack_set_type(struct sec_jack_info *hi, int jack_type) |
| { |
| struct sec_jack_platform_data *pdata = hi->pdata; |
| |
| /* this can happen during slow inserts where we think we identified |
| * the type but then we get another interrupt and do it again |
| */ |
| if (jack_type == hi->cur_jack_type) { |
| if (jack_type != SEC_HEADSET_4POLE) |
| pdata->set_micbias_state(false); |
| return; |
| } |
| |
| if (jack_type == SEC_HEADSET_4POLE) { |
| /* for a 4 pole headset, enable detection of send/end key */ |
| if (hi->send_key_dev == NULL) |
| /* enable to get events again */ |
| hi->send_key_dev = platform_device_register_data(NULL, |
| GPIO_EVENT_DEV_NAME, |
| hi->dev_id, |
| &sec_jack_input_data, |
| sizeof(sec_jack_input_data)); |
| } else { |
| /* for all other jacks, disable send/end key detection */ |
| if (hi->send_key_dev != NULL) { |
| /* disable to prevent false events on next insert */ |
| platform_device_unregister(hi->send_key_dev); |
| hi->send_key_dev = NULL; |
| } |
| /* micbias is left enabled for 4pole and disabled otherwise */ |
| pdata->set_micbias_state(false); |
| } |
| |
| hi->cur_jack_type = jack_type; |
| pr_info("%s : jack_type = %d\n", __func__, jack_type); |
| |
| /* prevent suspend to allow user space to respond to switch */ |
| wake_lock_timeout(&hi->det_wake_lock, WAKE_LOCK_TIME); |
| |
| switch_set_state(&switch_jack_detection, jack_type); |
| } |
| |
| static void handle_jack_not_inserted(struct sec_jack_info *hi) |
| { |
| sec_jack_set_type(hi, SEC_JACK_NO_DEVICE); |
| hi->pdata->set_micbias_state(false); |
| } |
| |
| static void determine_jack_type(struct sec_jack_info *hi) |
| { |
| struct sec_jack_zone *zones = hi->pdata->zones; |
| int size = hi->pdata->num_zones; |
| int count[MAX_ZONE_LIMIT] = {0}; |
| int adc; |
| int i; |
| unsigned npolarity = !hi->pdata->det_active_high; |
| |
| while (gpio_get_value(hi->pdata->det_gpio) ^ npolarity) { |
| adc = hi->pdata->get_adc_value(); |
| pr_debug("%s: adc = %d\n", __func__, adc); |
| |
| /* determine the type of headset based on the |
| * adc value. An adc value can fall in various |
| * ranges or zones. Within some ranges, the type |
| * can be returned immediately. Within others, the |
| * value is considered unstable and we need to sample |
| * a few more types (up to the limit determined by |
| * the range) before we return the type for that range. |
| */ |
| for (i = 0; i < size; i++) { |
| if (adc <= zones[i].adc_high) { |
| if (++count[i] > zones[i].check_count) { |
| sec_jack_set_type(hi, |
| zones[i].jack_type); |
| return; |
| } |
| msleep(zones[i].delay_ms); |
| break; |
| } |
| } |
| } |
| /* jack removed before detection complete */ |
| pr_debug("%s : jack removed before detection complete\n", __func__); |
| handle_jack_not_inserted(hi); |
| } |
| |
| /* thread run whenever the headset detect state changes (either insertion |
| * or removal). |
| */ |
| static irqreturn_t sec_jack_detect_irq(int irq, void *dev_id) |
| { |
| struct sec_jack_info *hi = dev_id; |
| |
| queue_work(hi->queue, &hi->detect_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| void sec_jack_detect_work(struct work_struct *work) |
| { |
| struct sec_jack_info *hi = |
| container_of(work, struct sec_jack_info, detect_work); |
| struct sec_jack_platform_data *pdata = hi->pdata; |
| int time_left_ms = DET_CHECK_TIME_MS; |
| unsigned npolarity = !hi->pdata->det_active_high; |
| |
| /* set mic bias to enable adc */ |
| pdata->set_micbias_state(true); |
| |
| /* debounce headset jack. don't try to determine the type of |
| * headset until the detect state is true for a while. |
| */ |
| while (time_left_ms > 0) { |
| if (!(gpio_get_value(hi->pdata->det_gpio) ^ npolarity)) { |
| /* jack not detected. */ |
| handle_jack_not_inserted(hi); |
| return; |
| } |
| msleep(10); |
| time_left_ms -= 10; |
| } |
| /* jack presence was detected the whole time, figure out which type */ |
| determine_jack_type(hi); |
| } |
| |
| /* thread run whenever the button of headset is pressed or released */ |
| void sec_jack_buttons_work(struct work_struct *work) |
| { |
| struct sec_jack_info *hi = |
| container_of(work, struct sec_jack_info, buttons_work); |
| struct sec_jack_platform_data *pdata = hi->pdata; |
| struct sec_jack_buttons_zone *btn_zones = pdata->buttons_zones; |
| int adc; |
| int i; |
| |
| /* when button is released */ |
| if (hi->pressed == 0) { |
| input_report_key(hi->input_dev, hi->pressed_code, 0); |
| input_sync(hi->input_dev); |
| pr_debug("%s: keycode=%d, is released\n", __func__, |
| hi->pressed_code); |
| return; |
| } |
| |
| /* when button is pressed */ |
| adc = pdata->get_adc_value(); |
| |
| for (i = 0; i < pdata->num_buttons_zones; i++) |
| if (adc >= btn_zones[i].adc_low && |
| adc <= btn_zones[i].adc_high) { |
| hi->pressed_code = btn_zones[i].code; |
| input_report_key(hi->input_dev, btn_zones[i].code, 1); |
| input_sync(hi->input_dev); |
| pr_debug("%s: keycode=%d, is pressed\n", __func__, |
| btn_zones[i].code); |
| return; |
| } |
| |
| pr_warn("%s: key is skipped. ADC value is %d\n", __func__, adc); |
| } |
| |
| static int sec_jack_probe(struct platform_device *pdev) |
| { |
| struct sec_jack_info *hi; |
| struct sec_jack_platform_data *pdata = pdev->dev.platform_data; |
| int ret; |
| |
| pr_info("%s : Registering jack driver\n", __func__); |
| if (!pdata) { |
| pr_err("%s : pdata is NULL.\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (!pdata->get_adc_value || !pdata->zones || |
| !pdata->set_micbias_state || pdata->num_zones > MAX_ZONE_LIMIT) { |
| pr_err("%s : need to check pdata\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (atomic_xchg(&instantiated, 1)) { |
| pr_err("%s : already instantiated, can only have one\n", |
| __func__); |
| return -ENODEV; |
| } |
| |
| sec_jack_key_map[0].gpio = pdata->send_end_gpio; |
| |
| hi = kzalloc(sizeof(struct sec_jack_info), GFP_KERNEL); |
| if (hi == NULL) { |
| pr_err("%s : Failed to allocate memory.\n", __func__); |
| ret = -ENOMEM; |
| goto err_kzalloc; |
| } |
| |
| hi->pdata = pdata; |
| |
| /* make the id of our gpi_event device the same as our platform device, |
| * which makes it the responsiblity of the board file to make sure |
| * it is unique relative to other gpio_event devices |
| */ |
| hi->dev_id = pdev->id; |
| |
| ret = gpio_request(pdata->det_gpio, "ear_jack_detect"); |
| if (ret) { |
| pr_err("%s : gpio_request failed for %d\n", |
| __func__, pdata->det_gpio); |
| goto err_gpio_request; |
| } |
| |
| ret = switch_dev_register(&switch_jack_detection); |
| if (ret < 0) { |
| pr_err("%s : Failed to register switch device\n", __func__); |
| goto err_switch_dev_register; |
| } |
| |
| wake_lock_init(&hi->det_wake_lock, WAKE_LOCK_SUSPEND, "sec_jack_det"); |
| |
| INIT_WORK(&hi->buttons_work, sec_jack_buttons_work); |
| INIT_WORK(&hi->detect_work, sec_jack_detect_work); |
| hi->queue = create_freezable_workqueue("sec_jack_wq"); |
| if (hi->queue == NULL) { |
| ret = -ENOMEM; |
| pr_err("%s: Failed to create workqueue\n", __func__); |
| goto err_create_wq_failed; |
| } |
| queue_work(hi->queue, &hi->detect_work); |
| |
| hi->det_irq = gpio_to_irq(pdata->det_gpio); |
| |
| set_bit(EV_KEY, hi->ids.evbit); |
| hi->ids.flags = INPUT_DEVICE_ID_MATCH_EVBIT; |
| hi->handler.filter = sec_jack_buttons_filter; |
| hi->handler.connect = sec_jack_buttons_connect; |
| hi->handler.disconnect = sec_jack_buttons_disconnect; |
| hi->handler.name = "sec_jack_buttons"; |
| hi->handler.id_table = &hi->ids; |
| hi->handler.private = hi; |
| |
| ret = input_register_handler(&hi->handler); |
| if (ret) { |
| pr_err("%s : Failed to register_handler\n", __func__); |
| goto err_register_input_handler; |
| } |
| ret = request_irq(hi->det_irq, sec_jack_detect_irq, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | |
| IRQF_ONESHOT, "sec_headset_detect", hi); |
| if (ret) { |
| pr_err("%s : Failed to request_irq.\n", __func__); |
| goto err_request_detect_irq; |
| } |
| |
| /* to handle insert/removal when we're sleeping in a call */ |
| ret = enable_irq_wake(hi->det_irq); |
| if (ret) { |
| pr_err("%s : Failed to enable_irq_wake.\n", __func__); |
| goto err_enable_irq_wake; |
| } |
| |
| dev_set_drvdata(&pdev->dev, hi); |
| |
| return 0; |
| |
| err_enable_irq_wake: |
| free_irq(hi->det_irq, hi); |
| err_request_detect_irq: |
| input_unregister_handler(&hi->handler); |
| err_register_input_handler: |
| destroy_workqueue(hi->queue); |
| err_create_wq_failed: |
| wake_lock_destroy(&hi->det_wake_lock); |
| switch_dev_unregister(&switch_jack_detection); |
| err_switch_dev_register: |
| gpio_free(pdata->det_gpio); |
| err_gpio_request: |
| kfree(hi); |
| err_kzalloc: |
| atomic_set(&instantiated, 0); |
| |
| return ret; |
| } |
| |
| static int sec_jack_remove(struct platform_device *pdev) |
| { |
| |
| struct sec_jack_info *hi = dev_get_drvdata(&pdev->dev); |
| |
| pr_info("%s :\n", __func__); |
| disable_irq_wake(hi->det_irq); |
| free_irq(hi->det_irq, hi); |
| destroy_workqueue(hi->queue); |
| if (hi->send_key_dev) { |
| platform_device_unregister(hi->send_key_dev); |
| hi->send_key_dev = NULL; |
| } |
| input_unregister_handler(&hi->handler); |
| wake_lock_destroy(&hi->det_wake_lock); |
| switch_dev_unregister(&switch_jack_detection); |
| gpio_free(hi->pdata->det_gpio); |
| kfree(hi); |
| atomic_set(&instantiated, 0); |
| |
| return 0; |
| } |
| |
| static struct platform_driver sec_jack_driver = { |
| .probe = sec_jack_probe, |
| .remove = sec_jack_remove, |
| .driver = { |
| .name = "sec_jack", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| static int __init sec_jack_init(void) |
| { |
| return platform_driver_register(&sec_jack_driver); |
| } |
| |
| static void __exit sec_jack_exit(void) |
| { |
| platform_driver_unregister(&sec_jack_driver); |
| } |
| |
| module_init(sec_jack_init); |
| module_exit(sec_jack_exit); |
| |
| MODULE_AUTHOR("[email protected]"); |
| MODULE_DESCRIPTION("Samsung Electronics Corp Ear-Jack detection driver"); |
| MODULE_LICENSE("GPL"); |