| /* NXP PCF50633 ADC Driver | 
 |  * | 
 |  * (C) 2006-2008 by Openmoko, Inc. | 
 |  * Author: Balaji Rao <[email protected]> | 
 |  * All rights reserved. | 
 |  * | 
 |  * Broken down from monstrous PCF50633 driver mainly by | 
 |  * Harald Welte, Andy Green and Werner Almesberger | 
 |  * | 
 |  *  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. | 
 |  * | 
 |  *  NOTE: This driver does not yet support subtractive ADC mode, which means | 
 |  *  you can do only one measurement per read request. | 
 |  */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/module.h> | 
 | #include <linux/device.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/completion.h> | 
 |  | 
 | #include <linux/mfd/pcf50633/core.h> | 
 | #include <linux/mfd/pcf50633/adc.h> | 
 |  | 
 | struct pcf50633_adc_request { | 
 | 	int mux; | 
 | 	int avg; | 
 | 	void (*callback)(struct pcf50633 *, void *, int); | 
 | 	void *callback_param; | 
 | }; | 
 |  | 
 | struct pcf50633_adc_sync_request { | 
 | 	int result; | 
 | 	struct completion completion; | 
 | }; | 
 |  | 
 | #define PCF50633_MAX_ADC_FIFO_DEPTH 8 | 
 |  | 
 | struct pcf50633_adc { | 
 | 	struct pcf50633 *pcf; | 
 |  | 
 | 	/* Private stuff */ | 
 | 	struct pcf50633_adc_request *queue[PCF50633_MAX_ADC_FIFO_DEPTH]; | 
 | 	int queue_head; | 
 | 	int queue_tail; | 
 | 	struct mutex queue_mutex; | 
 | }; | 
 |  | 
 | static inline struct pcf50633_adc *__to_adc(struct pcf50633 *pcf) | 
 | { | 
 | 	return platform_get_drvdata(pcf->adc_pdev); | 
 | } | 
 |  | 
 | static void adc_setup(struct pcf50633 *pcf, int channel, int avg) | 
 | { | 
 | 	channel &= PCF50633_ADCC1_ADCMUX_MASK; | 
 |  | 
 | 	/* kill ratiometric, but enable ACCSW biasing */ | 
 | 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC2, 0x00); | 
 | 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC3, 0x01); | 
 |  | 
 | 	/* start ADC conversion on selected channel */ | 
 | 	pcf50633_reg_write(pcf, PCF50633_REG_ADCC1, channel | avg | | 
 | 		    PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT); | 
 | } | 
 |  | 
 | static void trigger_next_adc_job_if_any(struct pcf50633 *pcf) | 
 | { | 
 | 	struct pcf50633_adc *adc = __to_adc(pcf); | 
 | 	int head; | 
 |  | 
 | 	head = adc->queue_head; | 
 |  | 
 | 	if (!adc->queue[head]) | 
 | 		return; | 
 |  | 
 | 	adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg); | 
 | } | 
 |  | 
 | static int | 
 | adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req) | 
 | { | 
 | 	struct pcf50633_adc *adc = __to_adc(pcf); | 
 | 	int head, tail; | 
 |  | 
 | 	mutex_lock(&adc->queue_mutex); | 
 |  | 
 | 	head = adc->queue_head; | 
 | 	tail = adc->queue_tail; | 
 |  | 
 | 	if (adc->queue[tail]) { | 
 | 		mutex_unlock(&adc->queue_mutex); | 
 | 		dev_err(pcf->dev, "ADC queue is full, dropping request\n"); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	adc->queue[tail] = req; | 
 | 	if (head == tail) | 
 | 		trigger_next_adc_job_if_any(pcf); | 
 | 	adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1); | 
 |  | 
 | 	mutex_unlock(&adc->queue_mutex); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, | 
 | 	int result) | 
 | { | 
 | 	struct pcf50633_adc_sync_request *req = param; | 
 |  | 
 | 	req->result = result; | 
 | 	complete(&req->completion); | 
 | } | 
 |  | 
 | int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg) | 
 | { | 
 | 	struct pcf50633_adc_sync_request req; | 
 | 	int ret; | 
 |  | 
 | 	init_completion(&req.completion); | 
 |  | 
 | 	ret = pcf50633_adc_async_read(pcf, mux, avg, | 
 | 		pcf50633_adc_sync_read_callback, &req); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	wait_for_completion(&req.completion); | 
 |  | 
 | 	return req.result; | 
 | } | 
 | EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read); | 
 |  | 
 | int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg, | 
 | 			     void (*callback)(struct pcf50633 *, void *, int), | 
 | 			     void *callback_param) | 
 | { | 
 | 	struct pcf50633_adc_request *req; | 
 |  | 
 | 	/* req is freed when the result is ready, in interrupt handler */ | 
 | 	req = kmalloc(sizeof(*req), GFP_KERNEL); | 
 | 	if (!req) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	req->mux = mux; | 
 | 	req->avg = avg; | 
 | 	req->callback = callback; | 
 | 	req->callback_param = callback_param; | 
 |  | 
 | 	return adc_enqueue_request(pcf, req); | 
 | } | 
 | EXPORT_SYMBOL_GPL(pcf50633_adc_async_read); | 
 |  | 
 | static int adc_result(struct pcf50633 *pcf) | 
 | { | 
 | 	u8 adcs1, adcs3; | 
 | 	u16 result; | 
 |  | 
 | 	adcs1 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS1); | 
 | 	adcs3 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS3); | 
 | 	result = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK); | 
 |  | 
 | 	dev_dbg(pcf->dev, "adc result = %d\n", result); | 
 |  | 
 | 	return result; | 
 | } | 
 |  | 
 | static void pcf50633_adc_irq(int irq, void *data) | 
 | { | 
 | 	struct pcf50633_adc *adc = data; | 
 | 	struct pcf50633 *pcf = adc->pcf; | 
 | 	struct pcf50633_adc_request *req; | 
 | 	int head, res; | 
 |  | 
 | 	mutex_lock(&adc->queue_mutex); | 
 | 	head = adc->queue_head; | 
 |  | 
 | 	req = adc->queue[head]; | 
 | 	if (WARN_ON(!req)) { | 
 | 		dev_err(pcf->dev, "pcf50633-adc irq: ADC queue empty!\n"); | 
 | 		mutex_unlock(&adc->queue_mutex); | 
 | 		return; | 
 | 	} | 
 | 	adc->queue[head] = NULL; | 
 | 	adc->queue_head = (head + 1) & | 
 | 				      (PCF50633_MAX_ADC_FIFO_DEPTH - 1); | 
 |  | 
 | 	res = adc_result(pcf); | 
 | 	trigger_next_adc_job_if_any(pcf); | 
 |  | 
 | 	mutex_unlock(&adc->queue_mutex); | 
 |  | 
 | 	req->callback(pcf, req->callback_param, res); | 
 | 	kfree(req); | 
 | } | 
 |  | 
 | static int pcf50633_adc_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct pcf50633_adc *adc; | 
 |  | 
 | 	adc = devm_kzalloc(&pdev->dev, sizeof(*adc), GFP_KERNEL); | 
 | 	if (!adc) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	adc->pcf = dev_to_pcf50633(pdev->dev.parent); | 
 | 	platform_set_drvdata(pdev, adc); | 
 |  | 
 | 	pcf50633_register_irq(adc->pcf, PCF50633_IRQ_ADCRDY, | 
 | 					pcf50633_adc_irq, adc); | 
 |  | 
 | 	mutex_init(&adc->queue_mutex); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int pcf50633_adc_remove(struct platform_device *pdev) | 
 | { | 
 | 	struct pcf50633_adc *adc = platform_get_drvdata(pdev); | 
 | 	int i, head; | 
 |  | 
 | 	pcf50633_free_irq(adc->pcf, PCF50633_IRQ_ADCRDY); | 
 |  | 
 | 	mutex_lock(&adc->queue_mutex); | 
 | 	head = adc->queue_head; | 
 |  | 
 | 	if (WARN_ON(adc->queue[head])) | 
 | 		dev_err(adc->pcf->dev, | 
 | 			"adc driver removed with request pending\n"); | 
 |  | 
 | 	for (i = 0; i < PCF50633_MAX_ADC_FIFO_DEPTH; i++) | 
 | 		kfree(adc->queue[i]); | 
 |  | 
 | 	mutex_unlock(&adc->queue_mutex); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct platform_driver pcf50633_adc_driver = { | 
 | 	.driver = { | 
 | 		.name = "pcf50633-adc", | 
 | 	}, | 
 | 	.probe = pcf50633_adc_probe, | 
 | 	.remove = pcf50633_adc_remove, | 
 | }; | 
 |  | 
 | module_platform_driver(pcf50633_adc_driver); | 
 |  | 
 | MODULE_AUTHOR("Balaji Rao <[email protected]>"); | 
 | MODULE_DESCRIPTION("PCF50633 adc driver"); | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_ALIAS("platform:pcf50633-adc"); | 
 |  |