xref: /openbmc/linux/drivers/mfd/pcf50633-adc.c (revision 8a105ca2)
108c3e06aSBalaji Rao /* NXP PCF50633 ADC Driver
208c3e06aSBalaji Rao  *
308c3e06aSBalaji Rao  * (C) 2006-2008 by Openmoko, Inc.
408c3e06aSBalaji Rao  * Author: Balaji Rao <balajirrao@openmoko.org>
508c3e06aSBalaji Rao  * All rights reserved.
608c3e06aSBalaji Rao  *
708c3e06aSBalaji Rao  * Broken down from monstrous PCF50633 driver mainly by
808c3e06aSBalaji Rao  * Harald Welte, Andy Green and Werner Almesberger
908c3e06aSBalaji Rao  *
1008c3e06aSBalaji Rao  *  This program is free software; you can redistribute  it and/or modify it
1108c3e06aSBalaji Rao  *  under  the terms of  the GNU General  Public License as published by the
1208c3e06aSBalaji Rao  *  Free Software Foundation;  either version 2 of the  License, or (at your
1308c3e06aSBalaji Rao  *  option) any later version.
1408c3e06aSBalaji Rao  *
1508c3e06aSBalaji Rao  *  NOTE: This driver does not yet support subtractive ADC mode, which means
1608c3e06aSBalaji Rao  *  you can do only one measurement per read request.
1708c3e06aSBalaji Rao  */
1808c3e06aSBalaji Rao 
1908c3e06aSBalaji Rao #include <linux/kernel.h>
205a0e3ad6STejun Heo #include <linux/slab.h>
2108c3e06aSBalaji Rao #include <linux/module.h>
2208c3e06aSBalaji Rao #include <linux/init.h>
2308c3e06aSBalaji Rao #include <linux/device.h>
2408c3e06aSBalaji Rao #include <linux/platform_device.h>
2508c3e06aSBalaji Rao #include <linux/completion.h>
2608c3e06aSBalaji Rao 
2708c3e06aSBalaji Rao #include <linux/mfd/pcf50633/core.h>
2808c3e06aSBalaji Rao #include <linux/mfd/pcf50633/adc.h>
2908c3e06aSBalaji Rao 
3008c3e06aSBalaji Rao struct pcf50633_adc_request {
3108c3e06aSBalaji Rao 	int mux;
3208c3e06aSBalaji Rao 	int avg;
3308c3e06aSBalaji Rao 	void (*callback)(struct pcf50633 *, void *, int);
3408c3e06aSBalaji Rao 	void *callback_param;
356438a694SLars-Peter Clausen };
3608c3e06aSBalaji Rao 
376438a694SLars-Peter Clausen struct pcf50633_adc_sync_request {
386438a694SLars-Peter Clausen 	int result;
3908c3e06aSBalaji Rao 	struct completion completion;
4008c3e06aSBalaji Rao };
4108c3e06aSBalaji Rao 
4208c3e06aSBalaji Rao #define PCF50633_MAX_ADC_FIFO_DEPTH 8
4308c3e06aSBalaji Rao 
4408c3e06aSBalaji Rao struct pcf50633_adc {
4508c3e06aSBalaji Rao 	struct pcf50633 *pcf;
4608c3e06aSBalaji Rao 
4708c3e06aSBalaji Rao 	/* Private stuff */
4808c3e06aSBalaji Rao 	struct pcf50633_adc_request *queue[PCF50633_MAX_ADC_FIFO_DEPTH];
4908c3e06aSBalaji Rao 	int queue_head;
5008c3e06aSBalaji Rao 	int queue_tail;
5108c3e06aSBalaji Rao 	struct mutex queue_mutex;
5208c3e06aSBalaji Rao };
5308c3e06aSBalaji Rao 
5408c3e06aSBalaji Rao static inline struct pcf50633_adc *__to_adc(struct pcf50633 *pcf)
5508c3e06aSBalaji Rao {
5608c3e06aSBalaji Rao 	return platform_get_drvdata(pcf->adc_pdev);
5708c3e06aSBalaji Rao }
5808c3e06aSBalaji Rao 
5908c3e06aSBalaji Rao static void adc_setup(struct pcf50633 *pcf, int channel, int avg)
6008c3e06aSBalaji Rao {
6108c3e06aSBalaji Rao 	channel &= PCF50633_ADCC1_ADCMUX_MASK;
6208c3e06aSBalaji Rao 
6308c3e06aSBalaji Rao 	/* kill ratiometric, but enable ACCSW biasing */
6408c3e06aSBalaji Rao 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC2, 0x00);
6508c3e06aSBalaji Rao 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC3, 0x01);
6608c3e06aSBalaji Rao 
6708c3e06aSBalaji Rao 	/* start ADC conversion on selected channel */
6808c3e06aSBalaji Rao 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC1, channel | avg |
6908c3e06aSBalaji Rao 		    PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT);
7008c3e06aSBalaji Rao }
7108c3e06aSBalaji Rao 
7208c3e06aSBalaji Rao static void trigger_next_adc_job_if_any(struct pcf50633 *pcf)
7308c3e06aSBalaji Rao {
7408c3e06aSBalaji Rao 	struct pcf50633_adc *adc = __to_adc(pcf);
7508c3e06aSBalaji Rao 	int head;
7608c3e06aSBalaji Rao 
7708c3e06aSBalaji Rao 	head = adc->queue_head;
7808c3e06aSBalaji Rao 
79bd8ef102SPaul Fertser 	if (!adc->queue[head])
8008c3e06aSBalaji Rao 		return;
8108c3e06aSBalaji Rao 
8208c3e06aSBalaji Rao 	adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg);
8308c3e06aSBalaji Rao }
8408c3e06aSBalaji Rao 
8508c3e06aSBalaji Rao static int
8608c3e06aSBalaji Rao adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req)
8708c3e06aSBalaji Rao {
8808c3e06aSBalaji Rao 	struct pcf50633_adc *adc = __to_adc(pcf);
8908c3e06aSBalaji Rao 	int head, tail;
9008c3e06aSBalaji Rao 
9108c3e06aSBalaji Rao 	mutex_lock(&adc->queue_mutex);
9208c3e06aSBalaji Rao 
9308c3e06aSBalaji Rao 	head = adc->queue_head;
9408c3e06aSBalaji Rao 	tail = adc->queue_tail;
9508c3e06aSBalaji Rao 
9608c3e06aSBalaji Rao 	if (adc->queue[tail]) {
9708c3e06aSBalaji Rao 		mutex_unlock(&adc->queue_mutex);
98bd8ef102SPaul Fertser 		dev_err(pcf->dev, "ADC queue is full, dropping request\n");
9908c3e06aSBalaji Rao 		return -EBUSY;
10008c3e06aSBalaji Rao 	}
10108c3e06aSBalaji Rao 
10208c3e06aSBalaji Rao 	adc->queue[tail] = req;
103bd8ef102SPaul Fertser 	if (head == tail)
104bd8ef102SPaul Fertser 		trigger_next_adc_job_if_any(pcf);
10508c3e06aSBalaji Rao 	adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
10608c3e06aSBalaji Rao 
10708c3e06aSBalaji Rao 	mutex_unlock(&adc->queue_mutex);
10808c3e06aSBalaji Rao 
10908c3e06aSBalaji Rao 	return 0;
11008c3e06aSBalaji Rao }
11108c3e06aSBalaji Rao 
1126438a694SLars-Peter Clausen static void pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param,
1136438a694SLars-Peter Clausen 	int result)
11408c3e06aSBalaji Rao {
1156438a694SLars-Peter Clausen 	struct pcf50633_adc_sync_request *req = param;
11608c3e06aSBalaji Rao 
11708c3e06aSBalaji Rao 	req->result = result;
11808c3e06aSBalaji Rao 	complete(&req->completion);
11908c3e06aSBalaji Rao }
12008c3e06aSBalaji Rao 
12108c3e06aSBalaji Rao int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg)
12208c3e06aSBalaji Rao {
1236438a694SLars-Peter Clausen 	struct pcf50633_adc_sync_request req;
1246438a694SLars-Peter Clausen 	int ret;
12508c3e06aSBalaji Rao 
1266438a694SLars-Peter Clausen 	init_completion(&req.completion);
12708c3e06aSBalaji Rao 
1286438a694SLars-Peter Clausen 	ret = pcf50633_adc_async_read(pcf, mux, avg,
1296438a694SLars-Peter Clausen 		pcf50633_adc_sync_read_callback, &req);
1306438a694SLars-Peter Clausen 	if (ret)
1316438a694SLars-Peter Clausen 		return ret;
13208c3e06aSBalaji Rao 
1336438a694SLars-Peter Clausen 	wait_for_completion(&req.completion);
134bd8ef102SPaul Fertser 
1356438a694SLars-Peter Clausen 	return req.result;
13608c3e06aSBalaji Rao }
13708c3e06aSBalaji Rao EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read);
13808c3e06aSBalaji Rao 
13908c3e06aSBalaji Rao int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
14008c3e06aSBalaji Rao 			     void (*callback)(struct pcf50633 *, void *, int),
14108c3e06aSBalaji Rao 			     void *callback_param)
14208c3e06aSBalaji Rao {
14308c3e06aSBalaji Rao 	struct pcf50633_adc_request *req;
14408c3e06aSBalaji Rao 
14508c3e06aSBalaji Rao 	/* req is freed when the result is ready, in interrupt handler */
14608c3e06aSBalaji Rao 	req = kmalloc(sizeof(*req), GFP_KERNEL);
14708c3e06aSBalaji Rao 	if (!req)
14808c3e06aSBalaji Rao 		return -ENOMEM;
14908c3e06aSBalaji Rao 
15008c3e06aSBalaji Rao 	req->mux = mux;
15108c3e06aSBalaji Rao 	req->avg = avg;
15208c3e06aSBalaji Rao 	req->callback = callback;
15308c3e06aSBalaji Rao 	req->callback_param = callback_param;
15408c3e06aSBalaji Rao 
155bd8ef102SPaul Fertser 	return adc_enqueue_request(pcf, req);
15608c3e06aSBalaji Rao }
15708c3e06aSBalaji Rao EXPORT_SYMBOL_GPL(pcf50633_adc_async_read);
15808c3e06aSBalaji Rao 
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 
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 
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 
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