diff options
Diffstat (limited to 'drivers/iio/accel/bma220_core.c')
| -rw-r--r-- | drivers/iio/accel/bma220_core.c | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c new file mode 100644 index 000000000000..f32d875b994e --- /dev/null +++ b/drivers/iio/accel/bma220_core.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BMA220 Digital triaxial acceleration sensor driver + * + * Copyright (c) 2016,2020 Intel Corporation. + * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro> + */ + +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/types.h> + +#include <linux/iio/buffer.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include "bma220.h" + +#define BMA220_REG_ID 0x00 +#define BMA220_REG_REVISION_ID 0x01 +#define BMA220_REG_ACCEL_X 0x02 +#define BMA220_REG_ACCEL_Y 0x03 +#define BMA220_REG_ACCEL_Z 0x04 +#define BMA220_REG_CONF0 0x05 +#define BMA220_HIGH_DUR_MSK GENMASK(5, 0) +#define BMA220_HIGH_HY_MSK GENMASK(7, 6) +#define BMA220_REG_CONF1 0x06 +#define BMA220_HIGH_TH_MSK GENMASK(3, 0) +#define BMA220_LOW_TH_MSK GENMASK(7, 4) +#define BMA220_REG_CONF2 0x07 +#define BMA220_LOW_DUR_MSK GENMASK(5, 0) +#define BMA220_LOW_HY_MSK GENMASK(7, 6) +#define BMA220_REG_CONF3 0x08 +#define BMA220_TT_DUR_MSK GENMASK(2, 0) +#define BMA220_TT_TH_MSK GENMASK(6, 3) +#define BMA220_REG_CONF4 0x09 +#define BMA220_SLOPE_DUR_MSK GENMASK(1, 0) +#define BMA220_SLOPE_TH_MSK GENMASK(5, 2) +#define BMA220_REG_CONF5 0x0a +#define BMA220_TIP_EN_MSK BIT(4) +#define BMA220_REG_IF0 0x0b +#define BMA220_REG_IF1 0x0c +#define BMA220_IF_SLOPE BIT(0) +#define BMA220_IF_DRDY BIT(1) +#define BMA220_IF_HIGH BIT(2) +#define BMA220_IF_LOW BIT(3) +#define BMA220_IF_TT BIT(4) +#define BMA220_REG_IE0 0x0d +#define BMA220_INT_EN_TAP_Z_MSK BIT(0) +#define BMA220_INT_EN_TAP_Y_MSK BIT(1) +#define BMA220_INT_EN_TAP_X_MSK BIT(2) +#define BMA220_INT_EN_SLOPE_Z_MSK BIT(3) +#define BMA220_INT_EN_SLOPE_Y_MSK BIT(4) +#define BMA220_INT_EN_SLOPE_X_MSK BIT(5) +#define BMA220_INT_EN_DRDY_MSK BIT(7) +#define BMA220_REG_IE1 0x0e +#define BMA220_INT_EN_HIGH_Z_MSK BIT(0) +#define BMA220_INT_EN_HIGH_Y_MSK BIT(1) +#define BMA220_INT_EN_HIGH_X_MSK BIT(2) +#define BMA220_INT_EN_LOW_MSK BIT(3) +#define BMA220_INT_LATCH_MSK GENMASK(6, 4) +#define BMA220_INT_RST_MSK BIT(7) +#define BMA220_REG_IE2 0x0f +#define BMA220_REG_FILTER 0x10 +#define BMA220_FILTER_MASK GENMASK(3, 0) +#define BMA220_REG_RANGE 0x11 +#define BMA220_RANGE_MASK GENMASK(1, 0) +#define BMA220_REG_SUSPEND 0x18 +#define BMA220_REG_SOFTRESET 0x19 + +#define BMA220_CHIP_ID 0xDD +#define BMA220_SUSPEND_SLEEP 0xFF +#define BMA220_SUSPEND_WAKE 0x00 +#define BMA220_RESET_MODE 0xFF +#define BMA220_NONRESET_MODE 0x00 + +#define BMA220_DEVICE_NAME "bma220" + +#define BMA220_COF_1000Hz 0x0 +#define BMA220_COF_500Hz 0x1 +#define BMA220_COF_250Hz 0x2 +#define BMA220_COF_125Hz 0x3 +#define BMA220_COF_64Hz 0x4 +#define BMA220_COF_32Hz 0x5 + +#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) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) |\ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 6, \ + .storagebits = 8, \ + .shift = 2, \ + .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 regmap *regmap; + struct mutex lock; + u8 lpf_3dB_freq_idx; + u8 range_idx; + struct iio_trigger *trig; + struct { + s8 chans[3]; + /* Ensure timestamp is naturally aligned. */ + aligned_s64 timestamp; + } scan __aligned(IIO_DMA_MINALIGN); +}; + +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), +}; + +/* Available cut-off frequencies of the low pass filter in Hz. */ +static const int bma220_lpf_3dB_freq_Hz_table[] = { + [BMA220_COF_1000Hz] = 1000, + [BMA220_COF_500Hz] = 500, + [BMA220_COF_250Hz] = 250, + [BMA220_COF_125Hz] = 125, + [BMA220_COF_64Hz] = 64, + [BMA220_COF_32Hz] = 32, +}; + +static const unsigned long bma220_accel_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0 +}; + +static bool bma220_is_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMA220_REG_CONF0: + case BMA220_REG_CONF1: + case BMA220_REG_CONF2: + case BMA220_REG_CONF3: + case BMA220_REG_CONF4: + case BMA220_REG_CONF5: + case BMA220_REG_IE0: + case BMA220_REG_IE1: + case BMA220_REG_IE2: + case BMA220_REG_FILTER: + case BMA220_REG_RANGE: + case BMA220_REG_WDT: + return true; + default: + return false; + } +} + +const struct regmap_config bma220_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = BIT(7), + .max_register = BMA220_REG_SOFTRESET, + .cache_type = REGCACHE_NONE, + .writeable_reg = bma220_is_writable_reg, +}; +EXPORT_SYMBOL_NS_GPL(bma220_spi_regmap_config, "IIO_BOSCH_BMA220"); + +/* + * Based on the datasheet the memory map differs between the SPI and the I2C + * implementations. I2C register addresses are simply shifted to the left + * by 1 bit yet the register size remains unchanged. + * This driver employs the SPI memory map to correlate register names to + * addresses regardless of the bus type. + */ + +const struct regmap_config bma220_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .reg_shift = -1, + .max_register = BMA220_REG_SOFTRESET, + .cache_type = REGCACHE_NONE, + .writeable_reg = bma220_is_writable_reg, +}; +EXPORT_SYMBOL_NS_GPL(bma220_i2c_regmap_config, "IIO_BOSCH_BMA220"); + +static int bma220_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bma220_data *data = iio_priv(indio_dev); + + return regmap_update_bits(data->regmap, BMA220_REG_IE0, + BMA220_INT_EN_DRDY_MSK, + FIELD_PREP(BMA220_INT_EN_DRDY_MSK, state)); +} + +static const struct iio_trigger_ops bma220_trigger_ops = { + .set_trigger_state = &bma220_data_rdy_trigger_set_state, + .validate_device = &iio_trigger_validate_own_device, +}; + +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); + + ret = regmap_bulk_read(data->regmap, BMA220_REG_ACCEL_X, + &data->scan.chans, + sizeof(data->scan.chans)); + if (ret < 0) + return IRQ_NONE; + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + iio_get_time_ns(indio_dev)); + 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 index; + unsigned int reg; + struct bma220_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, chan->address, ®); + if (ret < 0) + return -EINVAL; + *val = sign_extend32(reg >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + index = data->range_idx; + *val = bma220_scale_table[index][0]; + *val2 = bma220_scale_table[index][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + index = data->lpf_3dB_freq_idx; + *val = bma220_lpf_3dB_freq_Hz_table[index]; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int bma220_find_match_2dt(const int (*tbl)[2], const int n, + const int val, const int val2) +{ + int i; + + for (i = 0; i < n; i++) { + if (tbl[i][0] == val && tbl[i][1] == val2) + return i; + } + + return -EINVAL; +} + +static int bma220_find_match(const int *arr, const int n, const int val) +{ + int i; + + for (i = 0; i < n; i++) { + if (arr[i] == val) + return i; + } + + 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 ret; + int index = -1; + struct bma220_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->lock); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + index = bma220_find_match_2dt(bma220_scale_table, + ARRAY_SIZE(bma220_scale_table), + val, val2); + if (index < 0) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, BMA220_REG_RANGE, + BMA220_RANGE_MASK, + FIELD_PREP(BMA220_RANGE_MASK, index)); + if (ret < 0) + return ret; + data->range_idx = index; + + return 0; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + index = bma220_find_match(bma220_lpf_3dB_freq_Hz_table, + ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table), + val); + if (index < 0) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, BMA220_REG_FILTER, + BMA220_FILTER_MASK, + FIELD_PREP(BMA220_FILTER_MASK, index)); + if (ret < 0) + return ret; + data->lpf_3dB_freq_idx = index; + + 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; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)bma220_lpf_3dB_freq_Hz_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int bma220_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct bma220_data *data = iio_priv(indio_dev); + + if (readval) + return regmap_read(data->regmap, reg, readval); + return regmap_write(data->regmap, reg, writeval); +} + +static const struct iio_info bma220_info = { + .read_raw = bma220_read_raw, + .write_raw = bma220_write_raw, + .read_avail = bma220_read_avail, + .debugfs_reg_access = &bma220_reg_access, +}; + +static int bma220_reset(struct bma220_data *data, bool up) +{ + int ret; + unsigned int i, val; + + /* + * The chip can be reset by a simple register read. + * We need up to 2 register reads of the softreset register + * to make sure that the device is in the desired state. + */ + for (i = 0; i < 2; i++) { + ret = regmap_read(data->regmap, BMA220_REG_SOFTRESET, &val); + if (ret < 0) + return ret; + + if (up && val == BMA220_RESET_MODE) + return 0; + + if (!up && val == BMA220_NONRESET_MODE) + return 0; + } + + return -EBUSY; +} + +static int bma220_power(struct bma220_data *data, bool up) +{ + int ret; + unsigned int i, val; + + /* + * 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 = regmap_read(data->regmap, BMA220_REG_SUSPEND, &val); + if (ret < 0) + return ret; + + if (up && val == BMA220_SUSPEND_SLEEP) + return 0; + + if (!up && val == BMA220_SUSPEND_WAKE) + return 0; + } + + return -EBUSY; +} + +static int bma220_init(struct device *dev, struct bma220_data *data) +{ + int ret; + unsigned int val; + static const char * const regulator_names[] = { "vddd", "vddio", "vdda" }; + + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = regmap_read(data->regmap, BMA220_REG_ID, &val); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read chip id register\n"); + + if (val != BMA220_CHIP_ID) + dev_info(dev, "Unknown chip found: 0x%02x\n", val); + + ret = bma220_power(data, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to power-on chip\n"); + + ret = bma220_reset(data, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to soft reset chip\n"); + + return 0; +} + +static void bma220_deinit(void *data_ptr) +{ + struct bma220_data *data = data_ptr; + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = bma220_power(data, false); + if (ret) + dev_warn(dev, + "Failed to put device into suspend mode (%pe)\n", + ERR_PTR(ret)); +} + +static irqreturn_t bma220_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bma220_data *data = iio_priv(indio_dev); + int ret; + unsigned int bma220_reg_if1; + + ret = regmap_read(data->regmap, BMA220_REG_IF1, &bma220_reg_if1); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(BMA220_IF_DRDY, bma220_reg_if1)) + iio_trigger_poll_nested(data->trig); + + return IRQ_HANDLED; +} + +int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq) +{ + int ret; + struct iio_dev *indio_dev; + struct bma220_data *data; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + + ret = bma220_init(dev, data); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + 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; + + if (irq > 0) { + data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &bma220_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = devm_iio_trigger_register(dev, data->trig); + if (ret) + return dev_err_probe(dev, ret, + "iio trigger register fail\n"); + indio_dev->trig = iio_trigger_get(data->trig); + ret = devm_request_threaded_irq(dev, irq, NULL, + &bma220_irq_handler, IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "request irq %d failed\n", irq); + } + + ret = devm_add_action_or_reset(dev, bma220_deinit, data); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, + bma220_trigger_handler, NULL); + if (ret < 0) + dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS_GPL(bma220_common_probe, "IIO_BOSCH_BMA220"); + +static int bma220_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bma220_data *data = iio_priv(indio_dev); + + return bma220_power(data, false); +} + +static int bma220_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bma220_data *data = iio_priv(indio_dev); + + return bma220_power(data, true); +} +EXPORT_NS_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume, + IIO_BOSCH_BMA220); + +MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>"); +MODULE_DESCRIPTION("BMA220 acceleration sensor driver"); +MODULE_LICENSE("GPL"); |
