2006-12-12 20:18:30 +03:00
/*
* Apple Motion Sensor driver
*
* Copyright ( C ) 2005 Stelian Pop ( stelian @ popies . net )
* Copyright ( C ) 2006 Michael Hanselmann ( linux - kernel @ hansmi . ch )
*
* 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 .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/errno.h>
# include <linux/init.h>
2008-05-23 10:25:50 +04:00
# include <linux/of_platform.h>
2006-12-12 20:18:30 +03:00
# include <asm/pmac_pfunc.h>
# include "ams.h"
/* There is only one motion sensor per machine */
struct ams ams_info ;
static unsigned int verbose ;
module_param ( verbose , bool , 0644 ) ;
MODULE_PARM_DESC ( verbose , " Show free falls and shocks in kernel output " ) ;
/* Call with ams_info.lock held! */
void ams_sensors ( s8 * x , s8 * y , s8 * z )
{
u32 orient = ams_info . vflag ? ams_info . orient1 : ams_info . orient2 ;
if ( orient & 0x80 )
/* X and Y swapped */
ams_info . get_xyz ( y , x , z ) ;
else
ams_info . get_xyz ( x , y , z ) ;
if ( orient & 0x04 )
* z = ~ ( * z ) ;
if ( orient & 0x02 )
* y = ~ ( * y ) ;
if ( orient & 0x01 )
* x = ~ ( * x ) ;
}
static ssize_t ams_show_current ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
s8 x , y , z ;
mutex_lock ( & ams_info . lock ) ;
ams_sensors ( & x , & y , & z ) ;
mutex_unlock ( & ams_info . lock ) ;
return snprintf ( buf , PAGE_SIZE , " %d %d %d \n " , x , y , z ) ;
}
static DEVICE_ATTR ( current , S_IRUGO , ams_show_current , NULL ) ;
static void ams_handle_irq ( void * data )
{
enum ams_irq irq = * ( ( enum ams_irq * ) data ) ;
spin_lock ( & ams_info . irq_lock ) ;
ams_info . worker_irqs | = irq ;
schedule_work ( & ams_info . worker ) ;
spin_unlock ( & ams_info . irq_lock ) ;
}
static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL ;
static struct pmf_irq_client ams_freefall_client = {
. owner = THIS_MODULE ,
. handler = ams_handle_irq ,
. data = & ams_freefall_irq_data ,
} ;
static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK ;
static struct pmf_irq_client ams_shock_client = {
. owner = THIS_MODULE ,
. handler = ams_handle_irq ,
. data = & ams_shock_irq_data ,
} ;
/* Once hard disk parking is implemented in the kernel, this function can
* trigger it .
*/
static void ams_worker ( struct work_struct * work )
{
2008-10-17 19:51:13 +04:00
unsigned long flags ;
u8 irqs_to_clear ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:13 +04:00
mutex_lock ( & ams_info . lock ) ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:13 +04:00
spin_lock_irqsave ( & ams_info . irq_lock , flags ) ;
irqs_to_clear = ams_info . worker_irqs ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:13 +04:00
if ( ams_info . worker_irqs & AMS_IRQ_FREEFALL ) {
if ( verbose )
printk ( KERN_INFO " ams: freefall detected! \n " ) ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:13 +04:00
ams_info . worker_irqs & = ~ AMS_IRQ_FREEFALL ;
}
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:13 +04:00
if ( ams_info . worker_irqs & AMS_IRQ_SHOCK ) {
if ( verbose )
printk ( KERN_INFO " ams: shock detected! \n " ) ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:13 +04:00
ams_info . worker_irqs & = ~ AMS_IRQ_SHOCK ;
}
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:13 +04:00
spin_unlock_irqrestore ( & ams_info . irq_lock , flags ) ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:13 +04:00
ams_info . clear_irq ( irqs_to_clear ) ;
2006-12-12 20:18:30 +03:00
mutex_unlock ( & ams_info . lock ) ;
}
/* Call with ams_info.lock held! */
int ams_sensor_attach ( void )
{
int result ;
2007-04-03 04:52:17 +04:00
const u32 * prop ;
2006-12-12 20:18:30 +03:00
/* Get orientation */
2007-05-01 07:54:02 +04:00
prop = of_get_property ( ams_info . of_node , " orientation " , NULL ) ;
2006-12-12 20:18:30 +03:00
if ( ! prop )
return - ENODEV ;
ams_info . orient1 = * prop ;
ams_info . orient2 = * ( prop + 1 ) ;
/* Register freefall interrupt handler */
result = pmf_register_irq_client ( ams_info . of_node ,
" accel-int-1 " ,
& ams_freefall_client ) ;
if ( result < 0 )
return - ENODEV ;
/* Reset saved irqs */
ams_info . worker_irqs = 0 ;
/* Register shock interrupt handler */
result = pmf_register_irq_client ( ams_info . of_node ,
" accel-int-2 " ,
& ams_shock_client ) ;
if ( result < 0 )
goto release_freefall ;
/* Create device */
ams_info . of_dev = of_platform_device_create ( ams_info . of_node , " ams " , NULL ) ;
if ( ! ams_info . of_dev ) {
result = - ENODEV ;
goto release_shock ;
}
/* Create attributes */
result = device_create_file ( & ams_info . of_dev - > dev , & dev_attr_current ) ;
if ( result )
goto release_of ;
ams_info . vflag = ! ! ( ams_info . get_vendor ( ) & 0x10 ) ;
/* Init input device */
result = ams_input_init ( ) ;
if ( result )
goto release_device_file ;
return result ;
release_device_file :
device_remove_file ( & ams_info . of_dev - > dev , & dev_attr_current ) ;
release_of :
of_device_unregister ( ams_info . of_dev ) ;
release_shock :
pmf_unregister_irq_client ( & ams_shock_client ) ;
release_freefall :
pmf_unregister_irq_client ( & ams_freefall_client ) ;
return result ;
}
int __init ams_init ( void )
{
struct device_node * np ;
spin_lock_init ( & ams_info . irq_lock ) ;
mutex_init ( & ams_info . lock ) ;
INIT_WORK ( & ams_info . worker , ams_worker ) ;
# ifdef CONFIG_SENSORS_AMS_I2C
np = of_find_node_by_name ( NULL , " accelerometer " ) ;
2007-05-03 11:26:52 +04:00
if ( np & & of_device_is_compatible ( np , " AAPL,accelerometer_1 " ) )
2006-12-12 20:18:30 +03:00
/* Found I2C motion sensor */
return ams_i2c_init ( np ) ;
# endif
# ifdef CONFIG_SENSORS_AMS_PMU
np = of_find_node_by_name ( NULL , " sms " ) ;
2007-05-03 11:26:52 +04:00
if ( np & & of_device_is_compatible ( np , " sms " ) )
2006-12-12 20:18:30 +03:00
/* Found PMU motion sensor */
return ams_pmu_init ( np ) ;
# endif
return - ENODEV ;
}
2010-01-31 07:03:23 +03:00
void ams_sensor_detach ( void )
2006-12-12 20:18:30 +03:00
{
2008-10-17 19:51:12 +04:00
/* Remove input device */
ams_input_exit ( ) ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:12 +04:00
/* Remove attributes */
device_remove_file ( & ams_info . of_dev - > dev , & dev_attr_current ) ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:12 +04:00
/* Flush interrupt worker
*
* We do this after ams_info . exit ( ) , because an interrupt might
* have arrived before disabling them .
*/
2010-12-24 17:59:07 +03:00
flush_work_sync ( & ams_info . worker ) ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:12 +04:00
/* Remove device */
of_device_unregister ( ams_info . of_dev ) ;
2006-12-12 20:18:30 +03:00
2008-10-17 19:51:12 +04:00
/* Remove handler */
pmf_unregister_irq_client ( & ams_shock_client ) ;
pmf_unregister_irq_client ( & ams_freefall_client ) ;
2006-12-12 20:18:30 +03:00
}
2010-01-31 07:03:23 +03:00
static void __exit ams_exit ( void )
{
/* Shut down implementation */
ams_info . exit ( ) ;
}
2006-12-12 20:18:30 +03:00
MODULE_AUTHOR ( " Stelian Pop, Michael Hanselmann " ) ;
MODULE_DESCRIPTION ( " Apple Motion Sensor driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
module_init ( ams_init ) ;
module_exit ( ams_exit ) ;