xref: /openbmc/linux/drivers/mfd/pcf50633-adc.c (revision 8b450dcf)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
208c3e06aSBalaji Rao /* NXP PCF50633 ADC Driver
308c3e06aSBalaji Rao  *
408c3e06aSBalaji Rao  * (C) 2006-2008 by Openmoko, Inc.
508c3e06aSBalaji Rao  * Author: Balaji Rao <balajirrao@openmoko.org>
608c3e06aSBalaji Rao  * All rights reserved.
708c3e06aSBalaji Rao  *
808c3e06aSBalaji Rao  * Broken down from monstrous PCF50633 driver mainly by
908c3e06aSBalaji Rao  * Harald Welte, Andy Green and Werner Almesberger
1008c3e06aSBalaji Rao  *
1108c3e06aSBalaji Rao  *  NOTE: This driver does not yet support subtractive ADC mode, which means
1208c3e06aSBalaji Rao  *  you can do only one measurement per read request.
1308c3e06aSBalaji Rao  */
1408c3e06aSBalaji Rao 
1508c3e06aSBalaji Rao #include <linux/kernel.h>
165a0e3ad6STejun Heo #include <linux/slab.h>
1708c3e06aSBalaji Rao #include <linux/module.h>
1808c3e06aSBalaji Rao #include <linux/device.h>
1908c3e06aSBalaji Rao #include <linux/platform_device.h>
2008c3e06aSBalaji Rao #include <linux/completion.h>
2108c3e06aSBalaji Rao 
2208c3e06aSBalaji Rao #include <linux/mfd/pcf50633/core.h>
2308c3e06aSBalaji Rao #include <linux/mfd/pcf50633/adc.h>
2408c3e06aSBalaji Rao 
2508c3e06aSBalaji Rao struct pcf50633_adc_request {
2608c3e06aSBalaji Rao 	int mux;
2708c3e06aSBalaji Rao 	int avg;
2808c3e06aSBalaji Rao 	void (*callback)(struct pcf50633 *, void *, int);
2908c3e06aSBalaji Rao 	void *callback_param;
306438a694SLars-Peter Clausen };
3108c3e06aSBalaji Rao 
326438a694SLars-Peter Clausen struct pcf50633_adc_sync_request {
336438a694SLars-Peter Clausen 	int result;
3408c3e06aSBalaji Rao 	struct completion completion;
3508c3e06aSBalaji Rao };
3608c3e06aSBalaji Rao 
3708c3e06aSBalaji Rao #define PCF50633_MAX_ADC_FIFO_DEPTH 8
3808c3e06aSBalaji Rao 
3908c3e06aSBalaji Rao struct pcf50633_adc {
4008c3e06aSBalaji Rao 	struct pcf50633 *pcf;
4108c3e06aSBalaji Rao 
4208c3e06aSBalaji Rao 	/* Private stuff */
4308c3e06aSBalaji Rao 	struct pcf50633_adc_request *queue[PCF50633_MAX_ADC_FIFO_DEPTH];
4408c3e06aSBalaji Rao 	int queue_head;
4508c3e06aSBalaji Rao 	int queue_tail;
4608c3e06aSBalaji Rao 	struct mutex queue_mutex;
4708c3e06aSBalaji Rao };
4808c3e06aSBalaji Rao 
__to_adc(struct pcf50633 * pcf)4908c3e06aSBalaji Rao static inline struct pcf50633_adc *__to_adc(struct pcf50633 *pcf)
5008c3e06aSBalaji Rao {
5108c3e06aSBalaji Rao 	return platform_get_drvdata(pcf->adc_pdev);
5208c3e06aSBalaji Rao }
5308c3e06aSBalaji Rao 
adc_setup(struct pcf50633 * pcf,int channel,int avg)5408c3e06aSBalaji Rao static void adc_setup(struct pcf50633 *pcf, int channel, int avg)
5508c3e06aSBalaji Rao {
5608c3e06aSBalaji Rao 	channel &= PCF50633_ADCC1_ADCMUX_MASK;
5708c3e06aSBalaji Rao 
5808c3e06aSBalaji Rao 	/* kill ratiometric, but enable ACCSW biasing */
5908c3e06aSBalaji Rao 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC2, 0x00);
6008c3e06aSBalaji Rao 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC3, 0x01);
6108c3e06aSBalaji Rao 
6208c3e06aSBalaji Rao 	/* start ADC conversion on selected channel */
6308c3e06aSBalaji Rao 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC1, channel | avg |
6408c3e06aSBalaji Rao 		    PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT);
6508c3e06aSBalaji Rao }
6608c3e06aSBalaji Rao 
trigger_next_adc_job_if_any(struct pcf50633 * pcf)6708c3e06aSBalaji Rao static void trigger_next_adc_job_if_any(struct pcf50633 *pcf)
6808c3e06aSBalaji Rao {
6908c3e06aSBalaji Rao 	struct pcf50633_adc *adc = __to_adc(pcf);
7008c3e06aSBalaji Rao 	int head;
7108c3e06aSBalaji Rao 
7208c3e06aSBalaji Rao 	head = adc->queue_head;
7308c3e06aSBalaji Rao 
74bd8ef102SPaul Fertser 	if (!adc->queue[head])
7508c3e06aSBalaji Rao 		return;
7608c3e06aSBalaji Rao 
7708c3e06aSBalaji Rao 	adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg);
7808c3e06aSBalaji Rao }
7908c3e06aSBalaji Rao 
8008c3e06aSBalaji Rao static int
adc_enqueue_request(struct pcf50633 * pcf,struct pcf50633_adc_request * req)8108c3e06aSBalaji Rao adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req)
8208c3e06aSBalaji Rao {
8308c3e06aSBalaji Rao 	struct pcf50633_adc *adc = __to_adc(pcf);
8408c3e06aSBalaji Rao 	int head, tail;
8508c3e06aSBalaji Rao 
8608c3e06aSBalaji Rao 	mutex_lock(&adc->queue_mutex);
8708c3e06aSBalaji Rao 
8808c3e06aSBalaji Rao 	head = adc->queue_head;
8908c3e06aSBalaji Rao 	tail = adc->queue_tail;
9008c3e06aSBalaji Rao 
9108c3e06aSBalaji Rao 	if (adc->queue[tail]) {
9208c3e06aSBalaji Rao 		mutex_unlock(&adc->queue_mutex);
93bd8ef102SPaul Fertser 		dev_err(pcf->dev, "ADC queue is full, dropping request\n");
9408c3e06aSBalaji Rao 		return -EBUSY;
9508c3e06aSBalaji Rao 	}
9608c3e06aSBalaji Rao 
9708c3e06aSBalaji Rao 	adc->queue[tail] = req;
98bd8ef102SPaul Fertser 	if (head == tail)
99bd8ef102SPaul Fertser 		trigger_next_adc_job_if_any(pcf);
10008c3e06aSBalaji Rao 	adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
10108c3e06aSBalaji Rao 
10208c3e06aSBalaji Rao 	mutex_unlock(&adc->queue_mutex);
10308c3e06aSBalaji Rao 
10408c3e06aSBalaji Rao 	return 0;
10508c3e06aSBalaji Rao }
10608c3e06aSBalaji Rao 
pcf50633_adc_sync_read_callback(struct pcf50633 * pcf,void * param,int result)1076438a694SLars-Peter Clausen static void pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param,
1086438a694SLars-Peter Clausen 	int result)
10908c3e06aSBalaji Rao {
1106438a694SLars-Peter Clausen 	struct pcf50633_adc_sync_request *req = param;
11108c3e06aSBalaji Rao 
11208c3e06aSBalaji Rao 	req->result = result;
11308c3e06aSBalaji Rao 	complete(&req->completion);
11408c3e06aSBalaji Rao }
11508c3e06aSBalaji Rao 
pcf50633_adc_sync_read(struct pcf50633 * pcf,int mux,int avg)11608c3e06aSBalaji Rao int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg)
11708c3e06aSBalaji Rao {
1186438a694SLars-Peter Clausen 	struct pcf50633_adc_sync_request req;
1196438a694SLars-Peter Clausen 	int ret;
12008c3e06aSBalaji Rao 
1216438a694SLars-Peter Clausen 	init_completion(&req.completion);
12208c3e06aSBalaji Rao 
1236438a694SLars-Peter Clausen 	ret = pcf50633_adc_async_read(pcf, mux, avg,
1246438a694SLars-Peter Clausen 		pcf50633_adc_sync_read_callback, &req);
1256438a694SLars-Peter Clausen 	if (ret)
1266438a694SLars-Peter Clausen 		return ret;
12708c3e06aSBalaji Rao 
1286438a694SLars-Peter Clausen 	wait_for_completion(&req.completion);
129bd8ef102SPaul Fertser 
1306438a694SLars-Peter Clausen 	return req.result;
13108c3e06aSBalaji Rao }
13208c3e06aSBalaji Rao EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read);
13308c3e06aSBalaji Rao 
pcf50633_adc_async_read(struct pcf50633 * pcf,int mux,int avg,void (* callback)(struct pcf50633 *,void *,int),void * callback_param)13408c3e06aSBalaji Rao int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
13508c3e06aSBalaji Rao 			     void (*callback)(struct pcf50633 *, void *, int),
13608c3e06aSBalaji Rao 			     void *callback_param)
13708c3e06aSBalaji Rao {
13808c3e06aSBalaji Rao 	struct pcf50633_adc_request *req;
139*8b450dcfSQiheng Lin 	int ret;
14008c3e06aSBalaji Rao 
14108c3e06aSBalaji Rao 	/* req is freed when the result is ready, in interrupt handler */
14208c3e06aSBalaji Rao 	req = kmalloc(sizeof(*req), GFP_KERNEL);
14308c3e06aSBalaji Rao 	if (!req)
14408c3e06aSBalaji Rao 		return -ENOMEM;
14508c3e06aSBalaji Rao 
14608c3e06aSBalaji Rao 	req->mux = mux;
14708c3e06aSBalaji Rao 	req->avg = avg;
14808c3e06aSBalaji Rao 	req->callback = callback;
14908c3e06aSBalaji Rao 	req->callback_param = callback_param;
15008c3e06aSBalaji Rao 
151*8b450dcfSQiheng Lin 	ret = adc_enqueue_request(pcf, req);
152*8b450dcfSQiheng Lin 	if (ret)
153*8b450dcfSQiheng Lin 		kfree(req);
154*8b450dcfSQiheng Lin 
155*8b450dcfSQiheng Lin 	return ret;
15608c3e06aSBalaji Rao }
15708c3e06aSBalaji Rao EXPORT_SYMBOL_GPL(pcf50633_adc_async_read);
15808c3e06aSBalaji Rao 
adc_result(struct pcf50633 * pcf)15908c3e06aSBalaji Rao static int adc_result(struct pcf50633 *pcf)
16008c3e06aSBalaji Rao {
16108c3e06aSBalaji Rao 	u8 adcs1, adcs3;
16208c3e06aSBalaji Rao 	u16 result;
16308c3e06aSBalaji Rao 
16408c3e06aSBalaji Rao 	adcs1 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS1);
16508c3e06aSBalaji Rao 	adcs3 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS3);
16608c3e06aSBalaji Rao 	result = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK);
16708c3e06aSBalaji Rao 
16808c3e06aSBalaji Rao 	dev_dbg(pcf->dev, "adc result = %d\n", result);
16908c3e06aSBalaji Rao 
17008c3e06aSBalaji Rao 	return result;
17108c3e06aSBalaji Rao }
17208c3e06aSBalaji Rao 
pcf50633_adc_irq(int irq,void * data)17308c3e06aSBalaji Rao static void pcf50633_adc_irq(int irq, void *data)
17408c3e06aSBalaji Rao {
17508c3e06aSBalaji Rao 	struct pcf50633_adc *adc = data;
17608c3e06aSBalaji Rao 	struct pcf50633 *pcf = adc->pcf;
17708c3e06aSBalaji Rao 	struct pcf50633_adc_request *req;
178bd8ef102SPaul Fertser 	int head, res;
17908c3e06aSBalaji Rao 
18008c3e06aSBalaji Rao 	mutex_lock(&adc->queue_mutex);
18108c3e06aSBalaji Rao 	head = adc->queue_head;
18208c3e06aSBalaji Rao 
18308c3e06aSBalaji Rao 	req = adc->queue[head];
18408c3e06aSBalaji Rao 	if (WARN_ON(!req)) {
18508c3e06aSBalaji Rao 		dev_err(pcf->dev, "pcf50633-adc irq: ADC queue empty!\n");
18608c3e06aSBalaji Rao 		mutex_unlock(&adc->queue_mutex);
18708c3e06aSBalaji Rao 		return;
18808c3e06aSBalaji Rao 	}
18908c3e06aSBalaji Rao 	adc->queue[head] = NULL;
19008c3e06aSBalaji Rao 	adc->queue_head = (head + 1) &
19108c3e06aSBalaji Rao 				      (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
19208c3e06aSBalaji Rao 
193bd8ef102SPaul Fertser 	res = adc_result(pcf);
194bd8ef102SPaul Fertser 	trigger_next_adc_job_if_any(pcf);
195bd8ef102SPaul Fertser 
19608c3e06aSBalaji Rao 	mutex_unlock(&adc->queue_mutex);
19708c3e06aSBalaji Rao 
198bd8ef102SPaul Fertser 	req->callback(pcf, req->callback_param, res);
19908c3e06aSBalaji Rao 	kfree(req);
20008c3e06aSBalaji Rao }
20108c3e06aSBalaji Rao 
pcf50633_adc_probe(struct platform_device * pdev)202f791be49SBill Pemberton static int pcf50633_adc_probe(struct platform_device *pdev)
20308c3e06aSBalaji Rao {
20408c3e06aSBalaji Rao 	struct pcf50633_adc *adc;
20508c3e06aSBalaji Rao 
2068a105ca2SJingoo Han 	adc = devm_kzalloc(&pdev->dev, sizeof(*adc), GFP_KERNEL);
20708c3e06aSBalaji Rao 	if (!adc)
20808c3e06aSBalaji Rao 		return -ENOMEM;
20908c3e06aSBalaji Rao 
21068d641efSLars-Peter Clausen 	adc->pcf = dev_to_pcf50633(pdev->dev.parent);
21108c3e06aSBalaji Rao 	platform_set_drvdata(pdev, adc);
21208c3e06aSBalaji Rao 
21368d641efSLars-Peter Clausen 	pcf50633_register_irq(adc->pcf, PCF50633_IRQ_ADCRDY,
21408c3e06aSBalaji Rao 					pcf50633_adc_irq, adc);
21508c3e06aSBalaji Rao 
21608c3e06aSBalaji Rao 	mutex_init(&adc->queue_mutex);
21708c3e06aSBalaji Rao 
21808c3e06aSBalaji Rao 	return 0;
21908c3e06aSBalaji Rao }
22008c3e06aSBalaji Rao 
pcf50633_adc_remove(struct platform_device * pdev)2214740f73fSBill Pemberton static int pcf50633_adc_remove(struct platform_device *pdev)
22208c3e06aSBalaji Rao {
22308c3e06aSBalaji Rao 	struct pcf50633_adc *adc = platform_get_drvdata(pdev);
22408c3e06aSBalaji Rao 	int i, head;
22508c3e06aSBalaji Rao 
22608c3e06aSBalaji Rao 	pcf50633_free_irq(adc->pcf, PCF50633_IRQ_ADCRDY);
22708c3e06aSBalaji Rao 
22808c3e06aSBalaji Rao 	mutex_lock(&adc->queue_mutex);
22908c3e06aSBalaji Rao 	head = adc->queue_head;
23008c3e06aSBalaji Rao 
23108c3e06aSBalaji Rao 	if (WARN_ON(adc->queue[head]))
23208c3e06aSBalaji Rao 		dev_err(adc->pcf->dev,
23308c3e06aSBalaji Rao 			"adc driver removed with request pending\n");
23408c3e06aSBalaji Rao 
23508c3e06aSBalaji Rao 	for (i = 0; i < PCF50633_MAX_ADC_FIFO_DEPTH; i++)
23608c3e06aSBalaji Rao 		kfree(adc->queue[i]);
23708c3e06aSBalaji Rao 
23808c3e06aSBalaji Rao 	mutex_unlock(&adc->queue_mutex);
23908c3e06aSBalaji Rao 
24008c3e06aSBalaji Rao 	return 0;
24108c3e06aSBalaji Rao }
24208c3e06aSBalaji Rao 
24308c3e06aSBalaji Rao static struct platform_driver pcf50633_adc_driver = {
24408c3e06aSBalaji Rao 	.driver = {
24508c3e06aSBalaji Rao 		.name = "pcf50633-adc",
24608c3e06aSBalaji Rao 	},
24708c3e06aSBalaji Rao 	.probe = pcf50633_adc_probe,
24884449216SBill Pemberton 	.remove = pcf50633_adc_remove,
24908c3e06aSBalaji Rao };
25008c3e06aSBalaji Rao 
25165349d60SMark Brown module_platform_driver(pcf50633_adc_driver);
25208c3e06aSBalaji Rao 
25308c3e06aSBalaji Rao MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
25408c3e06aSBalaji Rao MODULE_DESCRIPTION("PCF50633 adc driver");
25508c3e06aSBalaji Rao MODULE_LICENSE("GPL");
25608c3e06aSBalaji Rao MODULE_ALIAS("platform:pcf50633-adc");
25708c3e06aSBalaji Rao 
258