2019-05-29 16:57:44 -07:00
// SPDX-License-Identifier: GPL-2.0-only
2021-03-09 15:43:14 -08:00
/*
2016-05-05 18:48:55 +03:00
* BMA220 Digital triaxial acceleration sensor driver
*
2020-08-31 12:08:13 +03:00
* Copyright ( c ) 2016 , 2020 Intel Corporation .
2016-05-05 18:48:55 +03:00
*/
2020-08-31 12:08:12 +03:00
# include <linux/bits.h>
2016-05-05 18:48:55 +03:00
# include <linux/kernel.h>
2020-08-31 12:08:10 +03:00
# include <linux/mod_devicetable.h>
2016-05-05 18:48:55 +03:00
# include <linux/module.h>
2020-08-31 12:08:11 +03:00
# include <linux/spi/spi.h>
2016-05-16 14:58:23 +03:00
# include <linux/iio/buffer.h>
2016-05-05 18:48:55 +03:00
# include <linux/iio/iio.h>
# include <linux/iio/sysfs.h>
2016-05-16 14:58:23 +03:00
# include <linux/iio/trigger_consumer.h>
# include <linux/iio/triggered_buffer.h>
2016-05-05 18:48:55 +03:00
# define BMA220_REG_ID 0x00
# define BMA220_REG_ACCEL_X 0x02
# define BMA220_REG_ACCEL_Y 0x03
# define BMA220_REG_ACCEL_Z 0x04
# define BMA220_REG_RANGE 0x11
# define BMA220_REG_SUSPEND 0x18
# define BMA220_CHIP_ID 0xDD
2020-08-31 12:08:12 +03:00
# define BMA220_READ_MASK BIT(7)
# define BMA220_RANGE_MASK GENMASK(1, 0)
2016-05-05 18:48:55 +03:00
# define BMA220_SUSPEND_SLEEP 0xFF
# define BMA220_SUSPEND_WAKE 0x00
# define BMA220_DEVICE_NAME "bma220"
# define BMA220_ACCEL_CHANNEL(index, reg, axis) { \
. type = IIO_ACCEL , \
. address = reg , \
. modified = 1 , \
. channel2 = IIO_MOD_ # # axis , \
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) , \
. info_mask_shared_by_type = BIT ( IIO_CHAN_INFO_SCALE ) , \
2016-05-16 14:58:23 +03:00
. scan_index = index , \
. scan_type = { \
. sign = ' s ' , \
. realbits = 6 , \
. storagebits = 8 , \
2021-11-04 01:24:01 -07:00
. shift = 2 , \
2016-05-16 14:58:23 +03:00
. endianness = IIO_CPU , \
} , \
2016-05-05 18:48:55 +03:00
}
2016-05-16 14:58:23 +03:00
enum bma220_axis {
AXIS_X ,
AXIS_Y ,
AXIS_Z ,
} ;
2020-08-31 12:08:07 +03:00
static const int bma220_scale_table [ ] [ 2 ] = {
{ 0 , 623000 } , { 1 , 248000 } , { 2 , 491000 } , { 4 , 983000 } ,
2016-05-05 18:48:55 +03:00
} ;
struct bma220_data {
struct spi_device * spi_device ;
struct mutex lock ;
2021-05-01 18:01:04 +01:00
struct {
s8 chans [ 3 ] ;
/* Ensure timestamp is naturally aligned. */
s64 timestamp __aligned ( 8 ) ;
} scan ;
2022-05-08 18:55:45 +01:00
u8 tx_buf [ 2 ] __aligned ( IIO_DMA_MINALIGN ) ;
2016-05-05 18:48:55 +03:00
} ;
static const struct iio_chan_spec bma220_channels [ ] = {
BMA220_ACCEL_CHANNEL ( 0 , BMA220_REG_ACCEL_X , X ) ,
BMA220_ACCEL_CHANNEL ( 1 , BMA220_REG_ACCEL_Y , Y ) ,
BMA220_ACCEL_CHANNEL ( 2 , BMA220_REG_ACCEL_Z , Z ) ,
2016-05-16 14:58:23 +03:00
IIO_CHAN_SOFT_TIMESTAMP ( 3 ) ,
2016-05-05 18:48:55 +03:00
} ;
static inline int bma220_read_reg ( struct spi_device * spi , u8 reg )
{
return spi_w8r8 ( spi , reg | BMA220_READ_MASK ) ;
}
2016-05-16 14:58:23 +03:00
static const unsigned long bma220_accel_scan_masks [ ] = {
BIT ( AXIS_X ) | BIT ( AXIS_Y ) | BIT ( AXIS_Z ) ,
0
} ;
static irqreturn_t bma220_trigger_handler ( int irq , void * p )
{
int ret ;
struct iio_poll_func * pf = p ;
struct iio_dev * indio_dev = pf - > indio_dev ;
struct bma220_data * data = iio_priv ( indio_dev ) ;
struct spi_device * spi = data - > spi_device ;
mutex_lock ( & data - > lock ) ;
data - > tx_buf [ 0 ] = BMA220_REG_ACCEL_X | BMA220_READ_MASK ;
2021-05-01 18:01:04 +01:00
ret = spi_write_then_read ( spi , data - > tx_buf , 1 , & data - > scan . chans ,
2016-05-16 14:58:23 +03:00
ARRAY_SIZE ( bma220_channels ) - 1 ) ;
if ( ret < 0 )
goto err ;
2021-05-01 18:01:04 +01:00
iio_push_to_buffers_with_timestamp ( indio_dev , & data - > scan ,
2016-05-16 14:58:23 +03:00
pf - > timestamp ) ;
err :
mutex_unlock ( & data - > lock ) ;
iio_trigger_notify_done ( indio_dev - > trig ) ;
return IRQ_HANDLED ;
}
2016-05-05 18:48:55 +03:00
static int bma220_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * val , int * val2 , long mask )
{
int ret ;
u8 range_idx ;
struct bma220_data * data = iio_priv ( indio_dev ) ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
ret = bma220_read_reg ( data - > spi_device , chan - > address ) ;
if ( ret < 0 )
return - EINVAL ;
2021-11-04 01:24:01 -07:00
* val = sign_extend32 ( ret > > chan - > scan_type . shift ,
chan - > scan_type . realbits - 1 ) ;
2016-05-05 18:48:55 +03:00
return IIO_VAL_INT ;
case IIO_CHAN_INFO_SCALE :
ret = bma220_read_reg ( data - > spi_device , BMA220_REG_RANGE ) ;
if ( ret < 0 )
return ret ;
range_idx = ret & BMA220_RANGE_MASK ;
* val = bma220_scale_table [ range_idx ] [ 0 ] ;
* val2 = bma220_scale_table [ range_idx ] [ 1 ] ;
return IIO_VAL_INT_PLUS_MICRO ;
}
return - EINVAL ;
}
static int bma220_write_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int val , int val2 , long mask )
{
int i ;
int ret ;
int index = - 1 ;
struct bma220_data * data = iio_priv ( indio_dev ) ;
switch ( mask ) {
case IIO_CHAN_INFO_SCALE :
for ( i = 0 ; i < ARRAY_SIZE ( bma220_scale_table ) ; i + + )
if ( val = = bma220_scale_table [ i ] [ 0 ] & &
val2 = = bma220_scale_table [ i ] [ 1 ] ) {
index = i ;
break ;
}
if ( index < 0 )
return - EINVAL ;
mutex_lock ( & data - > lock ) ;
data - > tx_buf [ 0 ] = BMA220_REG_RANGE ;
data - > tx_buf [ 1 ] = index ;
ret = spi_write ( data - > spi_device , data - > tx_buf ,
sizeof ( data - > tx_buf ) ) ;
if ( ret < 0 )
dev_err ( & data - > spi_device - > dev ,
" failed to set measurement range \n " ) ;
mutex_unlock ( & data - > lock ) ;
return 0 ;
}
return - EINVAL ;
}
2020-08-31 12:08:07 +03:00
static int bma220_read_avail ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
const int * * vals , int * type , int * length ,
long mask )
{
switch ( mask ) {
case IIO_CHAN_INFO_SCALE :
* vals = ( int * ) bma220_scale_table ;
* type = IIO_VAL_INT_PLUS_MICRO ;
* length = ARRAY_SIZE ( bma220_scale_table ) * 2 ;
return IIO_AVAIL_LIST ;
default :
return - EINVAL ;
}
}
2016-05-05 18:48:55 +03:00
static const struct iio_info bma220_info = {
. read_raw = bma220_read_raw ,
. write_raw = bma220_write_raw ,
2020-08-31 12:08:07 +03:00
. read_avail = bma220_read_avail ,
2016-05-05 18:48:55 +03:00
} ;
static int bma220_init ( struct spi_device * spi )
{
int ret ;
ret = bma220_read_reg ( spi , BMA220_REG_ID ) ;
if ( ret ! = BMA220_CHIP_ID )
return - ENODEV ;
/* Make sure the chip is powered on */
ret = bma220_read_reg ( spi , BMA220_REG_SUSPEND ) ;
2020-08-31 12:08:06 +03:00
if ( ret = = BMA220_SUSPEND_WAKE )
ret = bma220_read_reg ( spi , BMA220_REG_SUSPEND ) ;
2016-05-05 18:48:55 +03:00
if ( ret < 0 )
return ret ;
2020-08-31 12:08:06 +03:00
if ( ret = = BMA220_SUSPEND_WAKE )
return - EBUSY ;
2016-05-05 18:48:55 +03:00
return 0 ;
}
2021-06-25 17:01:37 +03:00
static int bma220_power ( struct spi_device * spi , bool up )
2016-05-05 18:48:55 +03:00
{
2021-06-25 17:01:37 +03:00
int i , ret ;
/**
* The chip can be suspended / woken up by a simple register read .
* So , we need up to 2 register reads of the suspend register
* to make sure that the device is in the desired state .
*/
for ( i = 0 ; i < 2 ; i + + ) {
ret = bma220_read_reg ( spi , BMA220_REG_SUSPEND ) ;
if ( ret < 0 )
return ret ;
2016-05-05 18:48:55 +03:00
2021-06-25 17:01:37 +03:00
if ( up & & ret = = BMA220_SUSPEND_SLEEP )
return 0 ;
if ( ! up & & ret = = BMA220_SUSPEND_WAKE )
return 0 ;
}
return - EBUSY ;
}
static void bma220_deinit ( void * spi )
{
bma220_power ( spi , false ) ;
2016-05-05 18:48:55 +03:00
}
static int bma220_probe ( struct spi_device * spi )
{
int ret ;
struct iio_dev * indio_dev ;
struct bma220_data * data ;
indio_dev = devm_iio_device_alloc ( & spi - > dev , sizeof ( * data ) ) ;
if ( ! indio_dev ) {
dev_err ( & spi - > dev , " iio allocation failed! \n " ) ;
return - ENOMEM ;
}
data = iio_priv ( indio_dev ) ;
data - > spi_device = spi ;
mutex_init ( & data - > lock ) ;
indio_dev - > info = & bma220_info ;
indio_dev - > name = BMA220_DEVICE_NAME ;
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > channels = bma220_channels ;
indio_dev - > num_channels = ARRAY_SIZE ( bma220_channels ) ;
2016-05-16 14:58:23 +03:00
indio_dev - > available_scan_masks = bma220_accel_scan_masks ;
2016-05-05 18:48:55 +03:00
ret = bma220_init ( data - > spi_device ) ;
2020-08-31 12:08:06 +03:00
if ( ret )
2016-05-05 18:48:55 +03:00
return ret ;
2021-06-25 17:01:36 +03:00
ret = devm_add_action_or_reset ( & spi - > dev , bma220_deinit , spi ) ;
if ( ret )
return ret ;
2016-05-16 14:58:23 +03:00
2021-06-25 17:01:36 +03:00
ret = devm_iio_triggered_buffer_setup ( & spi - > dev , indio_dev ,
iio_pollfunc_store_time ,
bma220_trigger_handler , NULL ) ;
2016-05-05 18:48:55 +03:00
if ( ret < 0 ) {
2021-06-25 17:01:36 +03:00
dev_err ( & spi - > dev , " iio triggered buffer setup failed \n " ) ;
return ret ;
2016-05-05 18:48:55 +03:00
}
2021-06-25 17:01:36 +03:00
return devm_iio_device_register ( & spi - > dev , indio_dev ) ;
2016-05-05 18:48:55 +03:00
}
2022-06-21 21:26:44 +01:00
static int bma220_suspend ( struct device * dev )
2016-05-05 18:48:55 +03:00
{
2021-06-25 17:01:37 +03:00
struct spi_device * spi = to_spi_device ( dev ) ;
2016-05-05 18:48:55 +03:00
2021-06-25 17:01:37 +03:00
return bma220_power ( spi , false ) ;
2016-05-05 18:48:55 +03:00
}
2022-06-21 21:26:44 +01:00
static int bma220_resume ( struct device * dev )
2016-05-05 18:48:55 +03:00
{
2021-06-25 17:01:37 +03:00
struct spi_device * spi = to_spi_device ( dev ) ;
2016-05-05 18:48:55 +03:00
2021-06-25 17:01:37 +03:00
return bma220_power ( spi , true ) ;
2016-05-05 18:48:55 +03:00
}
2022-06-21 21:26:44 +01:00
static DEFINE_SIMPLE_DEV_PM_OPS ( bma220_pm_ops , bma220_suspend , bma220_resume ) ;
2016-05-05 18:48:55 +03:00
static const struct spi_device_id bma220_spi_id [ ] = {
{ " bma220 " , 0 } ,
{ }
} ;
static const struct acpi_device_id bma220_acpi_id [ ] = {
{ " BMA0220 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( spi , bma220_spi_id ) ;
static struct spi_driver bma220_driver = {
. driver = {
. name = " bma220_spi " ,
2022-06-21 21:26:44 +01:00
. pm = pm_sleep_ptr ( & bma220_pm_ops ) ,
2020-08-31 12:08:10 +03:00
. acpi_match_table = bma220_acpi_id ,
2016-05-05 18:48:55 +03:00
} ,
. probe = bma220_probe ,
. id_table = bma220_spi_id ,
} ;
module_spi_driver ( bma220_driver ) ;
MODULE_AUTHOR ( " Tiberiu Breana <tiberiu.a.breana@intel.com> " ) ;
MODULE_DESCRIPTION ( " BMA220 acceleration sensor driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;