summaryrefslogtreecommitdiffstats
path: root/drivers/iio/accel/kxsd9.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/accel/kxsd9.c')
-rw-r--r--drivers/iio/accel/kxsd9.c436
1 files changed, 344 insertions, 92 deletions
diff --git a/drivers/iio/accel/kxsd9.c b/drivers/iio/accel/kxsd9.c
index da5fb67ecb34..9af60ac70738 100644
--- a/drivers/iio/accel/kxsd9.c
+++ b/drivers/iio/accel/kxsd9.c
@@ -12,19 +12,25 @@
* I have a suitable wire made up.
*
* TODO: Support the motion detector
- * Uses register address incrementing so could have a
- * heavily optimized ring buffer access function.
*/
#include <linux/device.h>
#include <linux/kernel.h>
-#include <linux/spi/spi.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/module.h>
-
+#include <linux/regmap.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+#include "kxsd9.h"
#define KXSD9_REG_X 0x00
#define KXSD9_REG_Y 0x02
@@ -33,28 +39,45 @@
#define KXSD9_REG_RESET 0x0a
#define KXSD9_REG_CTRL_C 0x0c
-#define KXSD9_FS_MASK 0x03
+#define KXSD9_CTRL_C_FS_MASK 0x03
+#define KXSD9_CTRL_C_FS_8G 0x00
+#define KXSD9_CTRL_C_FS_6G 0x01
+#define KXSD9_CTRL_C_FS_4G 0x02
+#define KXSD9_CTRL_C_FS_2G 0x03
+#define KXSD9_CTRL_C_MOT_LAT BIT(3)
+#define KXSD9_CTRL_C_MOT_LEV BIT(4)
+#define KXSD9_CTRL_C_LP_MASK 0xe0
+#define KXSD9_CTRL_C_LP_NONE 0x00
+#define KXSD9_CTRL_C_LP_2000HZC BIT(5)
+#define KXSD9_CTRL_C_LP_2000HZB BIT(6)
+#define KXSD9_CTRL_C_LP_2000HZA (BIT(5)|BIT(6))
+#define KXSD9_CTRL_C_LP_1000HZ BIT(7)
+#define KXSD9_CTRL_C_LP_500HZ (BIT(7)|BIT(5))
+#define KXSD9_CTRL_C_LP_100HZ (BIT(7)|BIT(6))
+#define KXSD9_CTRL_C_LP_50HZ (BIT(7)|BIT(6)|BIT(5))
#define KXSD9_REG_CTRL_B 0x0d
-#define KXSD9_REG_CTRL_A 0x0e
-#define KXSD9_READ(a) (0x80 | (a))
-#define KXSD9_WRITE(a) (a)
+#define KXSD9_CTRL_B_CLK_HLD BIT(7)
+#define KXSD9_CTRL_B_ENABLE BIT(6)
+#define KXSD9_CTRL_B_ST BIT(5) /* Self-test */
+
+#define KXSD9_REG_CTRL_A 0x0e
-#define KXSD9_STATE_RX_SIZE 2
-#define KXSD9_STATE_TX_SIZE 2
/**
* struct kxsd9_state - device related storage
- * @buf_lock: protect the rx and tx buffers.
- * @us: spi device
- * @rx: single rx buffer storage
- * @tx: single tx buffer storage
- **/
+ * @dev: pointer to the parent device
+ * @map: regmap to the device
+ * @orientation: mounting matrix, flipped axis etc
+ * @regs: regulators for this device, VDD and IOVDD
+ * @scale: the current scaling setting
+ */
struct kxsd9_state {
- struct mutex buf_lock;
- struct spi_device *us;
- u8 rx[KXSD9_STATE_RX_SIZE] ____cacheline_aligned;
- u8 tx[KXSD9_STATE_TX_SIZE];
+ struct device *dev;
+ struct regmap *map;
+ struct iio_mount_matrix orientation;
+ struct regulator_bulk_data regs[2];
+ u8 scale;
};
#define KXSD9_SCALE_2G "0.011978"
@@ -65,6 +88,14 @@ struct kxsd9_state {
/* reverse order */
static const int kxsd9_micro_scales[4] = { 47853, 35934, 23927, 11978 };
+#define KXSD9_ZERO_G_OFFSET -2048
+
+/*
+ * Regulator names
+ */
+static const char kxsd9_reg_vdd[] = "vdd";
+static const char kxsd9_reg_iovdd[] = "iovdd";
+
static int kxsd9_write_scale(struct iio_dev *indio_dev, int micro)
{
int ret, i;
@@ -79,42 +110,17 @@ static int kxsd9_write_scale(struct iio_dev *indio_dev, int micro)
if (!foundit)
return -EINVAL;
- mutex_lock(&st->buf_lock);
- ret = spi_w8r8(st->us, KXSD9_READ(KXSD9_REG_CTRL_C));
+ ret = regmap_update_bits(st->map,
+ KXSD9_REG_CTRL_C,
+ KXSD9_CTRL_C_FS_MASK,
+ i);
if (ret < 0)
goto error_ret;
- st->tx[0] = KXSD9_WRITE(KXSD9_REG_CTRL_C);
- st->tx[1] = (ret & ~KXSD9_FS_MASK) | i;
- ret = spi_write(st->us, st->tx, 2);
-error_ret:
- mutex_unlock(&st->buf_lock);
- return ret;
-}
+ /* Cached scale when the sensor is powered down */
+ st->scale = i;
-static int kxsd9_read(struct iio_dev *indio_dev, u8 address)
-{
- int ret;
- struct kxsd9_state *st = iio_priv(indio_dev);
- struct spi_transfer xfers[] = {
- {
- .bits_per_word = 8,
- .len = 1,
- .delay_usecs = 200,
- .tx_buf = st->tx,
- }, {
- .bits_per_word = 8,
- .len = 2,
- .rx_buf = st->rx,
- },
- };
-
- mutex_lock(&st->buf_lock);
- st->tx[0] = KXSD9_READ(address);
- ret = spi_sync_transfer(st->us, xfers, ARRAY_SIZE(xfers));
- if (!ret)
- ret = (((u16)(st->rx[0])) << 8) | (st->rx[1] & 0xF0);
- mutex_unlock(&st->buf_lock);
+error_ret:
return ret;
}
@@ -136,6 +142,9 @@ static int kxsd9_write_raw(struct iio_dev *indio_dev,
long mask)
{
int ret = -EINVAL;
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ pm_runtime_get_sync(st->dev);
if (mask == IIO_CHAN_INFO_SCALE) {
/* Check no integer component */
@@ -144,6 +153,9 @@ static int kxsd9_write_raw(struct iio_dev *indio_dev,
ret = kxsd9_write_scale(indio_dev, val2);
}
+ pm_runtime_mark_last_busy(st->dev);
+ pm_runtime_put_autosuspend(st->dev);
+
return ret;
}
@@ -153,45 +165,154 @@ static int kxsd9_read_raw(struct iio_dev *indio_dev,
{
int ret = -EINVAL;
struct kxsd9_state *st = iio_priv(indio_dev);
+ unsigned int regval;
+ __be16 raw_val;
+ u16 nval;
+
+ pm_runtime_get_sync(st->dev);
switch (mask) {
case IIO_CHAN_INFO_RAW:
- ret = kxsd9_read(indio_dev, chan->address);
- if (ret < 0)
+ ret = regmap_bulk_read(st->map, chan->address, &raw_val,
+ sizeof(raw_val));
+ if (ret)
goto error_ret;
- *val = ret;
+ nval = be16_to_cpu(raw_val);
+ /* Only 12 bits are valid */
+ nval >>= 4;
+ *val = nval;
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_CHAN_INFO_OFFSET:
+ /* This has a bias of -2048 */
+ *val = KXSD9_ZERO_G_OFFSET;
ret = IIO_VAL_INT;
break;
case IIO_CHAN_INFO_SCALE:
- ret = spi_w8r8(st->us, KXSD9_READ(KXSD9_REG_CTRL_C));
+ ret = regmap_read(st->map,
+ KXSD9_REG_CTRL_C,
+ &regval);
if (ret < 0)
goto error_ret;
- *val2 = kxsd9_micro_scales[ret & KXSD9_FS_MASK];
+ *val = 0;
+ *val2 = kxsd9_micro_scales[regval & KXSD9_CTRL_C_FS_MASK];
ret = IIO_VAL_INT_PLUS_MICRO;
break;
}
error_ret:
+ pm_runtime_mark_last_busy(st->dev);
+ pm_runtime_put_autosuspend(st->dev);
+
return ret;
};
-#define KXSD9_ACCEL_CHAN(axis) \
+
+static irqreturn_t kxsd9_trigger_handler(int irq, void *p)
+{
+ const struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct kxsd9_state *st = iio_priv(indio_dev);
+ int ret;
+ /* 4 * 16bit values AND timestamp */
+ __be16 hw_values[8];
+
+ ret = regmap_bulk_read(st->map,
+ KXSD9_REG_X,
+ &hw_values,
+ 8);
+ if (ret) {
+ dev_err(st->dev,
+ "error reading data\n");
+ return ret;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev,
+ hw_values,
+ iio_get_time_ns(indio_dev));
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int kxsd9_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ pm_runtime_get_sync(st->dev);
+
+ return 0;
+}
+
+static int kxsd9_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ pm_runtime_mark_last_busy(st->dev);
+ pm_runtime_put_autosuspend(st->dev);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops kxsd9_buffer_setup_ops = {
+ .preenable = kxsd9_buffer_preenable,
+ .postenable = iio_triggered_buffer_postenable,
+ .predisable = iio_triggered_buffer_predisable,
+ .postdisable = kxsd9_buffer_postdisable,
+};
+
+static const struct iio_mount_matrix *
+kxsd9_get_mount_matrix(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ return &st->orientation;
+}
+
+static const struct iio_chan_spec_ext_info kxsd9_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, kxsd9_get_mount_matrix),
+ { },
+};
+
+#define KXSD9_ACCEL_CHAN(axis, index) \
{ \
.type = IIO_ACCEL, \
.modified = 1, \
.channel2 = IIO_MOD_##axis, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
- .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+ .ext_info = kxsd9_ext_info, \
.address = KXSD9_REG_##axis, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .shift = 4, \
+ .endianness = IIO_BE, \
+ }, \
}
static const struct iio_chan_spec kxsd9_channels[] = {
- KXSD9_ACCEL_CHAN(X), KXSD9_ACCEL_CHAN(Y), KXSD9_ACCEL_CHAN(Z),
+ KXSD9_ACCEL_CHAN(X, 0),
+ KXSD9_ACCEL_CHAN(Y, 1),
+ KXSD9_ACCEL_CHAN(Z, 2),
{
.type = IIO_VOLTAGE,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.indexed = 1,
.address = KXSD9_REG_AUX,
- }
+ .scan_index = 3,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 12,
+ .storagebits = 16,
+ .shift = 4,
+ .endianness = IIO_BE,
+ },
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(4),
};
static const struct attribute_group kxsd9_attribute_group = {
@@ -202,17 +323,69 @@ static int kxsd9_power_up(struct kxsd9_state *st)
{
int ret;
- st->tx[0] = 0x0d;
- st->tx[1] = 0x40;
- ret = spi_write(st->us, st->tx, 2);
+ /* Enable the regulators */
+ ret = regulator_bulk_enable(ARRAY_SIZE(st->regs), st->regs);
+ if (ret) {
+ dev_err(st->dev, "Cannot enable regulators\n");
+ return ret;
+ }
+
+ /* Power up */
+ ret = regmap_write(st->map,
+ KXSD9_REG_CTRL_B,
+ KXSD9_CTRL_B_ENABLE);
if (ret)
return ret;
- st->tx[0] = 0x0c;
- st->tx[1] = 0x9b;
- return spi_write(st->us, st->tx, 2);
+ /*
+ * Set 1000Hz LPF, 2g fullscale, motion wakeup threshold 1g,
+ * latched wakeup
+ */
+ ret = regmap_write(st->map,
+ KXSD9_REG_CTRL_C,
+ KXSD9_CTRL_C_LP_1000HZ |
+ KXSD9_CTRL_C_MOT_LEV |
+ KXSD9_CTRL_C_MOT_LAT |
+ st->scale);
+ if (ret)
+ return ret;
+
+ /*
+ * Power-up time depends on the LPF setting, but typ 15.9 ms, let's
+ * set 20 ms to allow for some slack.
+ */
+ msleep(20);
+
+ return 0;
};
+static int kxsd9_power_down(struct kxsd9_state *st)
+{
+ int ret;
+
+ /*
+ * Set into low power mode - since there may be more users of the
+ * regulators this is the first step of the power saving: it will
+ * make sure we conserve power even if there are others users on the
+ * regulators.
+ */
+ ret = regmap_update_bits(st->map,
+ KXSD9_REG_CTRL_B,
+ KXSD9_CTRL_B_ENABLE,
+ 0);
+ if (ret)
+ return ret;
+
+ /* Disable the regulators */
+ ret = regulator_bulk_disable(ARRAY_SIZE(st->regs), st->regs);
+ if (ret) {
+ dev_err(st->dev, "Cannot disable regulators\n");
+ return ret;
+ }
+
+ return 0;
+}
+
static const struct iio_info kxsd9_info = {
.read_raw = &kxsd9_read_raw,
.write_raw = &kxsd9_write_raw,
@@ -220,57 +393,136 @@ static const struct iio_info kxsd9_info = {
.driver_module = THIS_MODULE,
};
-static int kxsd9_probe(struct spi_device *spi)
+/* Four channels apart from timestamp, scan mask = 0x0f */
+static const unsigned long kxsd9_scan_masks[] = { 0xf, 0 };
+
+int kxsd9_common_probe(struct device *dev,
+ struct regmap *map,
+ const char *name)
{
struct iio_dev *indio_dev;
struct kxsd9_state *st;
+ int ret;
- indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
if (!indio_dev)
return -ENOMEM;
st = iio_priv(indio_dev);
- spi_set_drvdata(spi, indio_dev);
+ st->dev = dev;
+ st->map = map;
- st->us = spi;
- mutex_init(&st->buf_lock);
indio_dev->channels = kxsd9_channels;
indio_dev->num_channels = ARRAY_SIZE(kxsd9_channels);
- indio_dev->name = spi_get_device_id(spi)->name;
- indio_dev->dev.parent = &spi->dev;
+ indio_dev->name = name;
+ indio_dev->dev.parent = dev;
indio_dev->info = &kxsd9_info;
indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->available_scan_masks = kxsd9_scan_masks;
+
+ /* Read the mounting matrix, if present */
+ ret = of_iio_read_mount_matrix(dev,
+ "mount-matrix",
+ &st->orientation);
+ if (ret)
+ return ret;
+
+ /* Fetch and turn on regulators */
+ st->regs[0].supply = kxsd9_reg_vdd;
+ st->regs[1].supply = kxsd9_reg_iovdd;
+ ret = devm_regulator_bulk_get(dev,
+ ARRAY_SIZE(st->regs),
+ st->regs);
+ if (ret) {
+ dev_err(dev, "Cannot get regulators\n");
+ return ret;
+ }
+ /* Default scaling */
+ st->scale = KXSD9_CTRL_C_FS_2G;
- spi->mode = SPI_MODE_0;
- spi_setup(spi);
kxsd9_power_up(st);
- return iio_device_register(indio_dev);
+ ret = iio_triggered_buffer_setup(indio_dev,
+ iio_pollfunc_store_time,
+ kxsd9_trigger_handler,
+ &kxsd9_buffer_setup_ops);
+ if (ret) {
+ dev_err(dev, "triggered buffer setup failed\n");
+ goto err_power_down;
+ }
+
+ ret = iio_device_register(indio_dev);
+ if (ret)
+ goto err_cleanup_buffer;
+
+ dev_set_drvdata(dev, indio_dev);
+
+ /* Enable runtime PM */
+ pm_runtime_get_noresume(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ /*
+ * Set autosuspend to two orders of magnitude larger than the
+ * start-up time. 20ms start-up time means 2000ms autosuspend,
+ * i.e. 2 seconds.
+ */
+ pm_runtime_set_autosuspend_delay(dev, 2000);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_put(dev);
+
+ return 0;
+
+err_cleanup_buffer:
+ iio_triggered_buffer_cleanup(indio_dev);
+err_power_down:
+ kxsd9_power_down(st);
+
+ return ret;
}
+EXPORT_SYMBOL(kxsd9_common_probe);
-static int kxsd9_remove(struct spi_device *spi)
+int kxsd9_common_remove(struct device *dev)
{
- iio_device_unregister(spi_get_drvdata(spi));
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ iio_triggered_buffer_cleanup(indio_dev);
+ iio_device_unregister(indio_dev);
+ pm_runtime_get_sync(dev);
+ pm_runtime_put_noidle(dev);
+ pm_runtime_disable(dev);
+ kxsd9_power_down(st);
return 0;
}
+EXPORT_SYMBOL(kxsd9_common_remove);
-static const struct spi_device_id kxsd9_id[] = {
- {"kxsd9", 0},
- { },
-};
-MODULE_DEVICE_TABLE(spi, kxsd9_id);
+#ifdef CONFIG_PM
+static int kxsd9_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct kxsd9_state *st = iio_priv(indio_dev);
-static struct spi_driver kxsd9_driver = {
- .driver = {
- .name = "kxsd9",
- },
- .probe = kxsd9_probe,
- .remove = kxsd9_remove,
- .id_table = kxsd9_id,
+ return kxsd9_power_down(st);
+}
+
+static int kxsd9_runtime_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct kxsd9_state *st = iio_priv(indio_dev);
+
+ return kxsd9_power_up(st);
+}
+#endif /* CONFIG_PM */
+
+const struct dev_pm_ops kxsd9_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(kxsd9_runtime_suspend,
+ kxsd9_runtime_resume, NULL)
};
-module_spi_driver(kxsd9_driver);
+EXPORT_SYMBOL(kxsd9_dev_pm_ops);
MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>");
-MODULE_DESCRIPTION("Kionix KXSD9 SPI driver");
+MODULE_DESCRIPTION("Kionix KXSD9 driver");
MODULE_LICENSE("GPL v2");