2009-01-09 03:49:26 +03:00
/* NXP PCF50633 ADC Driver
*
* ( C ) 2006 - 2008 by Openmoko , Inc .
* Author : Balaji Rao < balajirrao @ openmoko . org >
* 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/module.h>
# include <linux/init.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 ;
int result ;
void ( * callback ) ( struct pcf50633 * , void * , int ) ;
void * callback_param ;
/* Used in case of sync requests */
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 ;
2009-07-28 00:58:48 +04:00
if ( ! adc - > queue [ head ] )
2009-01-09 03:49:26 +03:00
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 ) ;
2009-07-28 00:58:48 +04:00
dev_err ( pcf - > dev , " ADC queue is full, dropping request \n " ) ;
2009-01-09 03:49:26 +03:00
return - EBUSY ;
}
adc - > queue [ tail ] = req ;
2009-07-28 00:58:48 +04:00
if ( head = = tail )
trigger_next_adc_job_if_any ( pcf ) ;
2009-01-09 03:49:26 +03:00
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_request * req = param ;
req - > result = result ;
complete ( & req - > completion ) ;
}
int pcf50633_adc_sync_read ( struct pcf50633 * pcf , int mux , int avg )
{
struct pcf50633_adc_request * req ;
2009-07-28 00:58:48 +04:00
int err ;
2009-01-09 03:49:26 +03:00
/* req is freed when the result is ready, in interrupt handler */
req = kzalloc ( sizeof ( * req ) , GFP_KERNEL ) ;
if ( ! req )
return - ENOMEM ;
req - > mux = mux ;
req - > avg = avg ;
req - > callback = pcf50633_adc_sync_read_callback ;
req - > callback_param = req ;
init_completion ( & req - > completion ) ;
2009-07-28 00:58:48 +04:00
err = adc_enqueue_request ( pcf , req ) ;
if ( err )
return err ;
2009-01-09 03:49:26 +03:00
wait_for_completion ( & req - > completion ) ;
2009-07-28 00:58:48 +04:00
/* FIXME by this time req might be already freed */
2009-01-09 03:49:26 +03:00
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 ;
2009-07-28 00:58:48 +04:00
return adc_enqueue_request ( pcf , req ) ;
2009-01-09 03:49:26 +03:00
}
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 ;
2009-07-28 00:58:48 +04:00
int head , res ;
2009-01-09 03:49:26 +03:00
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 ) ;
2009-07-28 00:58:48 +04:00
res = adc_result ( pcf ) ;
trigger_next_adc_job_if_any ( pcf ) ;
2009-01-09 03:49:26 +03:00
mutex_unlock ( & adc - > queue_mutex ) ;
2009-07-28 00:58:48 +04:00
req - > callback ( pcf , req - > callback_param , res ) ;
2009-01-09 03:49:26 +03:00
kfree ( req ) ;
}
static int __devinit pcf50633_adc_probe ( struct platform_device * pdev )
{
struct pcf50633_subdev_pdata * pdata = pdev - > dev . platform_data ;
struct pcf50633_adc * adc ;
adc = kzalloc ( sizeof ( * adc ) , GFP_KERNEL ) ;
if ( ! adc )
return - ENOMEM ;
adc - > pcf = pdata - > pcf ;
platform_set_drvdata ( pdev , adc ) ;
pcf50633_register_irq ( pdata - > pcf , PCF50633_IRQ_ADCRDY ,
pcf50633_adc_irq , adc ) ;
mutex_init ( & adc - > queue_mutex ) ;
return 0 ;
}
static int __devexit 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 ) ;
kfree ( adc ) ;
return 0 ;
}
static struct platform_driver pcf50633_adc_driver = {
. driver = {
. name = " pcf50633-adc " ,
} ,
. probe = pcf50633_adc_probe ,
. remove = __devexit_p ( pcf50633_adc_remove ) ,
} ;
static int __init pcf50633_adc_init ( void )
{
return platform_driver_register ( & pcf50633_adc_driver ) ;
}
module_init ( pcf50633_adc_init ) ;
static void __exit pcf50633_adc_exit ( void )
{
platform_driver_unregister ( & pcf50633_adc_driver ) ;
}
module_exit ( pcf50633_adc_exit ) ;
MODULE_AUTHOR ( " Balaji Rao <balajirrao@openmoko.org> " ) ;
MODULE_DESCRIPTION ( " PCF50633 adc driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:pcf50633-adc " ) ;