brintos

brintos / linux-shallow public Read only

0
0
Text · 5.4 KiB · 125016d Raw
211 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * drivers/extcon/extcon-adc-jack.c4 *5 * Analog Jack extcon driver with ADC-based detection capability.6 *7 * Copyright (C) 2016 Samsung Electronics8 * Chanwoo Choi <cw00.choi@samsung.com>9 *10 * Copyright (C) 2012 Samsung Electronics11 * MyungJoo Ham <myungjoo.ham@samsung.com>12 *13 * Modified for calling to IIO to get adc by <anish.singh@samsung.com>14 */15 16#include <linux/module.h>17#include <linux/slab.h>18#include <linux/device.h>19#include <linux/platform_device.h>20#include <linux/err.h>21#include <linux/interrupt.h>22#include <linux/workqueue.h>23#include <linux/iio/consumer.h>24#include <linux/extcon/extcon-adc-jack.h>25#include <linux/extcon-provider.h>26 27/**28 * struct adc_jack_data - internal data for adc_jack device driver29 * @dev:		The device structure associated with the adc_jack.30 * @edev:		extcon device.31 * @cable_names:	list of supported cables.32 * @adc_conditions:	list of adc value conditions.33 * @num_conditions:	size of adc_conditions.34 * @irq:		irq number of attach/detach event (0 if not exist).35 * @handling_delay:	interrupt handler will schedule extcon event36 *			handling at handling_delay jiffies.37 * @handler:		extcon event handler called by interrupt handler.38 * @chan:		iio channel being queried.39 * @wakeup_source:	Indicates if the device can wake up the system.40 */41struct adc_jack_data {42	struct device *dev;43	struct extcon_dev *edev;44 45	const unsigned int **cable_names;46	struct adc_jack_cond *adc_conditions;47	int num_conditions;48 49	int irq;50	unsigned long handling_delay; /* in jiffies */51	struct delayed_work handler;52 53	struct iio_channel *chan;54	bool wakeup_source;55};56 57static void adc_jack_handler(struct work_struct *work)58{59	struct adc_jack_data *data = container_of(to_delayed_work(work),60			struct adc_jack_data,61			handler);62	struct adc_jack_cond *def;63	int ret, adc_val;64	int i;65 66	ret = iio_read_channel_raw(data->chan, &adc_val);67	if (ret < 0) {68		dev_err(data->dev, "read channel() error: %d\n", ret);69		return;70	}71 72	/* Get state from adc value with adc_conditions */73	for (i = 0; i < data->num_conditions; i++) {74		def = &data->adc_conditions[i];75		if (def->min_adc <= adc_val && def->max_adc >= adc_val) {76			extcon_set_state_sync(data->edev, def->id, true);77			return;78		}79	}80 81	/* Set the detached state if adc value is not included in the range */82	for (i = 0; i < data->num_conditions; i++) {83		def = &data->adc_conditions[i];84		extcon_set_state_sync(data->edev, def->id, false);85	}86}87 88static irqreturn_t adc_jack_irq_thread(int irq, void *_data)89{90	struct adc_jack_data *data = _data;91 92	queue_delayed_work(system_power_efficient_wq,93			   &data->handler, data->handling_delay);94	return IRQ_HANDLED;95}96 97static int adc_jack_probe(struct platform_device *pdev)98{99	struct adc_jack_data *data;100	struct adc_jack_pdata *pdata = dev_get_platdata(&pdev->dev);101	int i, err = 0;102 103	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);104	if (!data)105		return -ENOMEM;106 107	if (!pdata->cable_names) {108		dev_err(&pdev->dev, "error: cable_names not defined.\n");109		return -EINVAL;110	}111 112	data->dev = &pdev->dev;113	data->edev = devm_extcon_dev_allocate(&pdev->dev, pdata->cable_names);114	if (IS_ERR(data->edev)) {115		dev_err(&pdev->dev, "failed to allocate extcon device\n");116		return -ENOMEM;117	}118 119	if (!pdata->adc_conditions) {120		dev_err(&pdev->dev, "error: adc_conditions not defined.\n");121		return -EINVAL;122	}123	data->adc_conditions = pdata->adc_conditions;124 125	/* Check the length of array and set num_conditions */126	for (i = 0; data->adc_conditions[i].id != EXTCON_NONE; i++);127	data->num_conditions = i;128 129	data->chan = devm_iio_channel_get(&pdev->dev, pdata->consumer_channel);130	if (IS_ERR(data->chan))131		return PTR_ERR(data->chan);132 133	data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms);134	data->wakeup_source = pdata->wakeup_source;135 136	INIT_DEFERRABLE_WORK(&data->handler, adc_jack_handler);137 138	platform_set_drvdata(pdev, data);139 140	err = devm_extcon_dev_register(&pdev->dev, data->edev);141	if (err)142		return err;143 144	data->irq = platform_get_irq(pdev, 0);145	if (data->irq < 0)146		return -ENODEV;147 148	err = request_any_context_irq(data->irq, adc_jack_irq_thread,149			pdata->irq_flags, pdata->name, data);150 151	if (err < 0) {152		dev_err(&pdev->dev, "error: irq %d\n", data->irq);153		return err;154	}155 156	if (data->wakeup_source)157		device_init_wakeup(&pdev->dev, 1);158 159	adc_jack_handler(&data->handler.work);160	return 0;161}162 163static void adc_jack_remove(struct platform_device *pdev)164{165	struct adc_jack_data *data = platform_get_drvdata(pdev);166 167	free_irq(data->irq, data);168	cancel_work_sync(&data->handler.work);169}170 171#ifdef CONFIG_PM_SLEEP172static int adc_jack_suspend(struct device *dev)173{174	struct adc_jack_data *data = dev_get_drvdata(dev);175 176	cancel_delayed_work_sync(&data->handler);177	if (device_may_wakeup(data->dev))178		enable_irq_wake(data->irq);179 180	return 0;181}182 183static int adc_jack_resume(struct device *dev)184{185	struct adc_jack_data *data = dev_get_drvdata(dev);186 187	if (device_may_wakeup(data->dev))188		disable_irq_wake(data->irq);189 190	return 0;191}192#endif /* CONFIG_PM_SLEEP */193 194static SIMPLE_DEV_PM_OPS(adc_jack_pm_ops,195		adc_jack_suspend, adc_jack_resume);196 197static struct platform_driver adc_jack_driver = {198	.probe          = adc_jack_probe,199	.remove_new     = adc_jack_remove,200	.driver         = {201		.name   = "adc-jack",202		.pm = &adc_jack_pm_ops,203	},204};205 206module_platform_driver(adc_jack_driver);207 208MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");209MODULE_DESCRIPTION("ADC Jack extcon driver");210MODULE_LICENSE("GPL v2");211