mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-17 09:14:19 +08:00
3ce868bb05
The datasheet mentions that the suspend mode is toggled by reading the suspend register. The reading returns value 0xFF if the system was in suspend mode, otherwise it returns value 0x00. The bma220_deinit() function does up to 2 reads, in case the device was in suspend mode, which suggests a level of paranoia that makes the logic in bma220_suspend() and bma220_resume() look insufficient. This change implements a bma220_power() function which does up to 2 reads of the suspend register to make sure that the chip enters a desired (suspended or normal) mode. If the transition fails, then -EBUSY is returned. Since only a reference to SPI device is required, we can remove the spi_set_drvdata() call and get the SPI device object from the base device object in the suspend/resume routines. Signed-off-by: Alexandru Ardelean <aardelean@deviqon.com> Link: https://lore.kernel.org/r/20210625140137.362282-2-aardelean@deviqon.com Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
332 lines
7.8 KiB
C
332 lines
7.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* BMA220 Digital triaxial acceleration sensor driver
|
|
*
|
|
* Copyright (c) 2016,2020 Intel Corporation.
|
|
*/
|
|
|
|
#include <linux/bits.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/module.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
#include <linux/iio/buffer.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/sysfs.h>
|
|
#include <linux/iio/trigger_consumer.h>
|
|
#include <linux/iio/triggered_buffer.h>
|
|
|
|
#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
|
|
#define BMA220_READ_MASK BIT(7)
|
|
#define BMA220_RANGE_MASK GENMASK(1, 0)
|
|
#define BMA220_DATA_SHIFT 2
|
|
#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), \
|
|
.scan_index = index, \
|
|
.scan_type = { \
|
|
.sign = 's', \
|
|
.realbits = 6, \
|
|
.storagebits = 8, \
|
|
.shift = BMA220_DATA_SHIFT, \
|
|
.endianness = IIO_CPU, \
|
|
}, \
|
|
}
|
|
|
|
enum bma220_axis {
|
|
AXIS_X,
|
|
AXIS_Y,
|
|
AXIS_Z,
|
|
};
|
|
|
|
static const int bma220_scale_table[][2] = {
|
|
{0, 623000}, {1, 248000}, {2, 491000}, {4, 983000},
|
|
};
|
|
|
|
struct bma220_data {
|
|
struct spi_device *spi_device;
|
|
struct mutex lock;
|
|
struct {
|
|
s8 chans[3];
|
|
/* Ensure timestamp is naturally aligned. */
|
|
s64 timestamp __aligned(8);
|
|
} scan;
|
|
u8 tx_buf[2] ____cacheline_aligned;
|
|
};
|
|
|
|
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),
|
|
IIO_CHAN_SOFT_TIMESTAMP(3),
|
|
};
|
|
|
|
static inline int bma220_read_reg(struct spi_device *spi, u8 reg)
|
|
{
|
|
return spi_w8r8(spi, reg | BMA220_READ_MASK);
|
|
}
|
|
|
|
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;
|
|
ret = spi_write_then_read(spi, data->tx_buf, 1, &data->scan.chans,
|
|
ARRAY_SIZE(bma220_channels) - 1);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
|
|
pf->timestamp);
|
|
err:
|
|
mutex_unlock(&data->lock);
|
|
iio_trigger_notify_done(indio_dev->trig);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
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;
|
|
*val = sign_extend32(ret >> BMA220_DATA_SHIFT, 5);
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static const struct iio_info bma220_info = {
|
|
.read_raw = bma220_read_raw,
|
|
.write_raw = bma220_write_raw,
|
|
.read_avail = bma220_read_avail,
|
|
};
|
|
|
|
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);
|
|
if (ret == BMA220_SUSPEND_WAKE)
|
|
ret = bma220_read_reg(spi, BMA220_REG_SUSPEND);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (ret == BMA220_SUSPEND_WAKE)
|
|
return -EBUSY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bma220_power(struct spi_device *spi, bool up)
|
|
{
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
indio_dev->available_scan_masks = bma220_accel_scan_masks;
|
|
|
|
ret = bma220_init(data->spi_device);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = devm_add_action_or_reset(&spi->dev, bma220_deinit, spi);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
|
|
iio_pollfunc_store_time,
|
|
bma220_trigger_handler, NULL);
|
|
if (ret < 0) {
|
|
dev_err(&spi->dev, "iio triggered buffer setup failed\n");
|
|
return ret;
|
|
}
|
|
|
|
return devm_iio_device_register(&spi->dev, indio_dev);
|
|
}
|
|
|
|
static __maybe_unused int bma220_suspend(struct device *dev)
|
|
{
|
|
struct spi_device *spi = to_spi_device(dev);
|
|
|
|
return bma220_power(spi, false);
|
|
}
|
|
|
|
static __maybe_unused int bma220_resume(struct device *dev)
|
|
{
|
|
struct spi_device *spi = to_spi_device(dev);
|
|
|
|
return bma220_power(spi, true);
|
|
}
|
|
static SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume);
|
|
|
|
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",
|
|
.pm = &bma220_pm_ops,
|
|
.acpi_match_table = bma220_acpi_id,
|
|
},
|
|
.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");
|