1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 219939860Sanish kumar /* 319939860Sanish kumar * drivers/extcon/extcon-adc-jack.c 419939860Sanish kumar * 519939860Sanish kumar * Analog Jack extcon driver with ADC-based detection capability. 619939860Sanish kumar * 7a7da72eeSChanwoo Choi * Copyright (C) 2016 Samsung Electronics 8a7da72eeSChanwoo Choi * Chanwoo Choi <cw00.choi@samsung.com> 9a7da72eeSChanwoo Choi * 1019939860Sanish kumar * Copyright (C) 2012 Samsung Electronics 1119939860Sanish kumar * MyungJoo Ham <myungjoo.ham@samsung.com> 1219939860Sanish kumar * 1319939860Sanish kumar * Modified for calling to IIO to get adc by <anish.singh@samsung.com> 1419939860Sanish kumar */ 1519939860Sanish kumar 16d9310e35SAxel Lin #include <linux/module.h> 1719939860Sanish kumar #include <linux/slab.h> 1819939860Sanish kumar #include <linux/device.h> 1919939860Sanish kumar #include <linux/platform_device.h> 2019939860Sanish kumar #include <linux/err.h> 2119939860Sanish kumar #include <linux/interrupt.h> 2219939860Sanish kumar #include <linux/workqueue.h> 2319939860Sanish kumar #include <linux/iio/consumer.h> 2419939860Sanish kumar #include <linux/extcon/extcon-adc-jack.h> 25176aa360SChanwoo Choi #include <linux/extcon-provider.h> 2619939860Sanish kumar 2719939860Sanish kumar /** 2819939860Sanish kumar * struct adc_jack_data - internal data for adc_jack device driver 29*3e8e45b6SYang Li * @dev: The device structure associated with the adc_jack. 30a75e1c73SChanwoo Choi * @edev: extcon device. 31a75e1c73SChanwoo Choi * @cable_names: list of supported cables. 32a75e1c73SChanwoo Choi * @adc_conditions: list of adc value conditions. 33a75e1c73SChanwoo Choi * @num_conditions: size of adc_conditions. 34a75e1c73SChanwoo Choi * @irq: irq number of attach/detach event (0 if not exist). 35a75e1c73SChanwoo Choi * @handling_delay: interrupt handler will schedule extcon event 3619939860Sanish kumar * handling at handling_delay jiffies. 37a75e1c73SChanwoo Choi * @handler: extcon event handler called by interrupt handler. 38a75e1c73SChanwoo Choi * @chan: iio channel being queried. 39*3e8e45b6SYang Li * @wakeup_source: Indicates if the device can wake up the system. 4019939860Sanish kumar */ 4119939860Sanish kumar struct adc_jack_data { 421b6cf310SVenkat Reddy Talla struct device *dev; 431876fd9aSChanwoo Choi struct extcon_dev *edev; 4419939860Sanish kumar 4573b6ecdbSChanwoo Choi const unsigned int **cable_names; 4619939860Sanish kumar struct adc_jack_cond *adc_conditions; 4719939860Sanish kumar int num_conditions; 4819939860Sanish kumar 4919939860Sanish kumar int irq; 5019939860Sanish kumar unsigned long handling_delay; /* in jiffies */ 5119939860Sanish kumar struct delayed_work handler; 5219939860Sanish kumar 5319939860Sanish kumar struct iio_channel *chan; 541b6cf310SVenkat Reddy Talla bool wakeup_source; 5519939860Sanish kumar }; 5619939860Sanish kumar 5719939860Sanish kumar static void adc_jack_handler(struct work_struct *work) 5819939860Sanish kumar { 5919939860Sanish kumar struct adc_jack_data *data = container_of(to_delayed_work(work), 6019939860Sanish kumar struct adc_jack_data, 6119939860Sanish kumar handler); 62a7da72eeSChanwoo Choi struct adc_jack_cond *def; 6319939860Sanish kumar int ret, adc_val; 6419939860Sanish kumar int i; 6519939860Sanish kumar 6619939860Sanish kumar ret = iio_read_channel_raw(data->chan, &adc_val); 6719939860Sanish kumar if (ret < 0) { 686e3a7e89SChanwoo Choi dev_err(data->dev, "read channel() error: %d\n", ret); 6919939860Sanish kumar return; 7019939860Sanish kumar } 7119939860Sanish kumar 7219939860Sanish kumar /* Get state from adc value with adc_conditions */ 7319939860Sanish kumar for (i = 0; i < data->num_conditions; i++) { 74a7da72eeSChanwoo Choi def = &data->adc_conditions[i]; 7519939860Sanish kumar if (def->min_adc <= adc_val && def->max_adc >= adc_val) { 768670b459SChanwoo Choi extcon_set_state_sync(data->edev, def->id, true); 77a7da72eeSChanwoo Choi return; 7819939860Sanish kumar } 7919939860Sanish kumar } 8019939860Sanish kumar 81a7da72eeSChanwoo Choi /* Set the detached state if adc value is not included in the range */ 82a7da72eeSChanwoo Choi for (i = 0; i < data->num_conditions; i++) { 83a7da72eeSChanwoo Choi def = &data->adc_conditions[i]; 848670b459SChanwoo Choi extcon_set_state_sync(data->edev, def->id, false); 85a7da72eeSChanwoo Choi } 8619939860Sanish kumar } 8719939860Sanish kumar 8819939860Sanish kumar static irqreturn_t adc_jack_irq_thread(int irq, void *_data) 8919939860Sanish kumar { 9019939860Sanish kumar struct adc_jack_data *data = _data; 9119939860Sanish kumar 921a82e81eSMark Brown queue_delayed_work(system_power_efficient_wq, 931a82e81eSMark Brown &data->handler, data->handling_delay); 9419939860Sanish kumar return IRQ_HANDLED; 9519939860Sanish kumar } 9619939860Sanish kumar 9744f34fd4SBill Pemberton static int adc_jack_probe(struct platform_device *pdev) 9819939860Sanish kumar { 9919939860Sanish kumar struct adc_jack_data *data; 1007c0f6558SJingoo Han struct adc_jack_pdata *pdata = dev_get_platdata(&pdev->dev); 10119939860Sanish kumar int i, err = 0; 10219939860Sanish kumar 10319939860Sanish kumar data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 10419939860Sanish kumar if (!data) 10519939860Sanish kumar return -ENOMEM; 10619939860Sanish kumar 10719939860Sanish kumar if (!pdata->cable_names) { 10819939860Sanish kumar dev_err(&pdev->dev, "error: cable_names not defined.\n"); 1094b5dd738SSangjung Woo return -EINVAL; 11019939860Sanish kumar } 11119939860Sanish kumar 1121b6cf310SVenkat Reddy Talla data->dev = &pdev->dev; 1131876fd9aSChanwoo Choi data->edev = devm_extcon_dev_allocate(&pdev->dev, pdata->cable_names); 1141876fd9aSChanwoo Choi if (IS_ERR(data->edev)) { 1151876fd9aSChanwoo Choi dev_err(&pdev->dev, "failed to allocate extcon device\n"); 1161876fd9aSChanwoo Choi return -ENOMEM; 1171876fd9aSChanwoo Choi } 11819939860Sanish kumar 119a7da72eeSChanwoo Choi if (!pdata->adc_conditions) { 12019939860Sanish kumar dev_err(&pdev->dev, "error: adc_conditions not defined.\n"); 1214b5dd738SSangjung Woo return -EINVAL; 12219939860Sanish kumar } 12319939860Sanish kumar data->adc_conditions = pdata->adc_conditions; 12419939860Sanish kumar 12519939860Sanish kumar /* Check the length of array and set num_conditions */ 126a7da72eeSChanwoo Choi for (i = 0; data->adc_conditions[i].id != EXTCON_NONE; i++); 12719939860Sanish kumar data->num_conditions = i; 12819939860Sanish kumar 129bc84cff2SChristophe JAILLET data->chan = devm_iio_channel_get(&pdev->dev, pdata->consumer_channel); 1304b5dd738SSangjung Woo if (IS_ERR(data->chan)) 1314b5dd738SSangjung Woo return PTR_ERR(data->chan); 13219939860Sanish kumar 13319939860Sanish kumar data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms); 1341b6cf310SVenkat Reddy Talla data->wakeup_source = pdata->wakeup_source; 13519939860Sanish kumar 136033d9959SLinus Torvalds INIT_DEFERRABLE_WORK(&data->handler, adc_jack_handler); 13719939860Sanish kumar 13819939860Sanish kumar platform_set_drvdata(pdev, data); 13919939860Sanish kumar 1401876fd9aSChanwoo Choi err = devm_extcon_dev_register(&pdev->dev, data->edev); 14119939860Sanish kumar if (err) 1424b5dd738SSangjung Woo return err; 14319939860Sanish kumar 14419939860Sanish kumar data->irq = platform_get_irq(pdev, 0); 145a3fc5723SStephen Boyd if (data->irq < 0) 1464b5dd738SSangjung Woo return -ENODEV; 14719939860Sanish kumar 14819939860Sanish kumar err = request_any_context_irq(data->irq, adc_jack_irq_thread, 14919939860Sanish kumar pdata->irq_flags, pdata->name, data); 15019939860Sanish kumar 15103019759SAxel Lin if (err < 0) { 15219939860Sanish kumar dev_err(&pdev->dev, "error: irq %d\n", data->irq); 1534b5dd738SSangjung Woo return err; 15419939860Sanish kumar } 15519939860Sanish kumar 1561b6cf310SVenkat Reddy Talla if (data->wakeup_source) 1571b6cf310SVenkat Reddy Talla device_init_wakeup(&pdev->dev, 1); 1581b6cf310SVenkat Reddy Talla 159ba4b2715SVenkat Reddy Talla adc_jack_handler(&data->handler.work); 16003019759SAxel Lin return 0; 16119939860Sanish kumar } 16219939860Sanish kumar 163b2da7e24SUwe Kleine-König static void adc_jack_remove(struct platform_device *pdev) 16419939860Sanish kumar { 16519939860Sanish kumar struct adc_jack_data *data = platform_get_drvdata(pdev); 16619939860Sanish kumar 16719939860Sanish kumar free_irq(data->irq, data); 16819939860Sanish kumar cancel_work_sync(&data->handler.work); 16919939860Sanish kumar } 17019939860Sanish kumar 1711b6cf310SVenkat Reddy Talla #ifdef CONFIG_PM_SLEEP 1721b6cf310SVenkat Reddy Talla static int adc_jack_suspend(struct device *dev) 1731b6cf310SVenkat Reddy Talla { 1741b6cf310SVenkat Reddy Talla struct adc_jack_data *data = dev_get_drvdata(dev); 1751b6cf310SVenkat Reddy Talla 1761b6cf310SVenkat Reddy Talla cancel_delayed_work_sync(&data->handler); 1771b6cf310SVenkat Reddy Talla if (device_may_wakeup(data->dev)) 1781b6cf310SVenkat Reddy Talla enable_irq_wake(data->irq); 1791b6cf310SVenkat Reddy Talla 1801b6cf310SVenkat Reddy Talla return 0; 1811b6cf310SVenkat Reddy Talla } 1821b6cf310SVenkat Reddy Talla 1831b6cf310SVenkat Reddy Talla static int adc_jack_resume(struct device *dev) 1841b6cf310SVenkat Reddy Talla { 1851b6cf310SVenkat Reddy Talla struct adc_jack_data *data = dev_get_drvdata(dev); 1861b6cf310SVenkat Reddy Talla 1871b6cf310SVenkat Reddy Talla if (device_may_wakeup(data->dev)) 1881b6cf310SVenkat Reddy Talla disable_irq_wake(data->irq); 1891b6cf310SVenkat Reddy Talla 1901b6cf310SVenkat Reddy Talla return 0; 1911b6cf310SVenkat Reddy Talla } 1921b6cf310SVenkat Reddy Talla #endif /* CONFIG_PM_SLEEP */ 1931b6cf310SVenkat Reddy Talla 1941b6cf310SVenkat Reddy Talla static SIMPLE_DEV_PM_OPS(adc_jack_pm_ops, 1951b6cf310SVenkat Reddy Talla adc_jack_suspend, adc_jack_resume); 1961b6cf310SVenkat Reddy Talla 19719939860Sanish kumar static struct platform_driver adc_jack_driver = { 19819939860Sanish kumar .probe = adc_jack_probe, 199b2da7e24SUwe Kleine-König .remove_new = adc_jack_remove, 20019939860Sanish kumar .driver = { 20119939860Sanish kumar .name = "adc-jack", 2021b6cf310SVenkat Reddy Talla .pm = &adc_jack_pm_ops, 20319939860Sanish kumar }, 20419939860Sanish kumar }; 20519939860Sanish kumar 20619939860Sanish kumar module_platform_driver(adc_jack_driver); 207d9310e35SAxel Lin 208d9310e35SAxel Lin MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); 209d9310e35SAxel Lin MODULE_DESCRIPTION("ADC Jack extcon driver"); 210d9310e35SAxel Lin MODULE_LICENSE("GPL v2"); 211