linux/drivers/iio/imu/fxos8700_core.c

649 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* FXOS8700 - NXP IMU (accelerometer plus magnetometer)
*
* IIO core driver for FXOS8700, with support for I2C/SPI busses
*
* TODO: Buffer, trigger, and IRQ support
*/
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include "fxos8700.h"
/* Register Definitions */
#define FXOS8700_STATUS 0x00
#define FXOS8700_OUT_X_MSB 0x01
#define FXOS8700_OUT_X_LSB 0x02
#define FXOS8700_OUT_Y_MSB 0x03
#define FXOS8700_OUT_Y_LSB 0x04
#define FXOS8700_OUT_Z_MSB 0x05
#define FXOS8700_OUT_Z_LSB 0x06
#define FXOS8700_F_SETUP 0x09
#define FXOS8700_TRIG_CFG 0x0a
#define FXOS8700_SYSMOD 0x0b
#define FXOS8700_INT_SOURCE 0x0c
#define FXOS8700_WHO_AM_I 0x0d
#define FXOS8700_XYZ_DATA_CFG 0x0e
#define FXOS8700_HP_FILTER_CUTOFF 0x0f
#define FXOS8700_PL_STATUS 0x10
#define FXOS8700_PL_CFG 0x11
#define FXOS8700_PL_COUNT 0x12
#define FXOS8700_PL_BF_ZCOMP 0x13
#define FXOS8700_PL_THS_REG 0x14
#define FXOS8700_A_FFMT_CFG 0x15
#define FXOS8700_A_FFMT_SRC 0x16
#define FXOS8700_A_FFMT_THS 0x17
#define FXOS8700_A_FFMT_COUNT 0x18
#define FXOS8700_TRANSIENT_CFG 0x1d
#define FXOS8700_TRANSIENT_SRC 0x1e
#define FXOS8700_TRANSIENT_THS 0x1f
#define FXOS8700_TRANSIENT_COUNT 0x20
#define FXOS8700_PULSE_CFG 0x21
#define FXOS8700_PULSE_SRC 0x22
#define FXOS8700_PULSE_THSX 0x23
#define FXOS8700_PULSE_THSY 0x24
#define FXOS8700_PULSE_THSZ 0x25
#define FXOS8700_PULSE_TMLT 0x26
#define FXOS8700_PULSE_LTCY 0x27
#define FXOS8700_PULSE_WIND 0x28
#define FXOS8700_ASLP_COUNT 0x29
#define FXOS8700_CTRL_REG1 0x2a
#define FXOS8700_CTRL_REG2 0x2b
#define FXOS8700_CTRL_REG3 0x2c
#define FXOS8700_CTRL_REG4 0x2d
#define FXOS8700_CTRL_REG5 0x2e
#define FXOS8700_OFF_X 0x2f
#define FXOS8700_OFF_Y 0x30
#define FXOS8700_OFF_Z 0x31
#define FXOS8700_M_DR_STATUS 0x32
#define FXOS8700_M_OUT_X_MSB 0x33
#define FXOS8700_M_OUT_X_LSB 0x34
#define FXOS8700_M_OUT_Y_MSB 0x35
#define FXOS8700_M_OUT_Y_LSB 0x36
#define FXOS8700_M_OUT_Z_MSB 0x37
#define FXOS8700_M_OUT_Z_LSB 0x38
#define FXOS8700_CMP_X_MSB 0x39
#define FXOS8700_CMP_X_LSB 0x3a
#define FXOS8700_CMP_Y_MSB 0x3b
#define FXOS8700_CMP_Y_LSB 0x3c
#define FXOS8700_CMP_Z_MSB 0x3d
#define FXOS8700_CMP_Z_LSB 0x3e
#define FXOS8700_M_OFF_X_MSB 0x3f
#define FXOS8700_M_OFF_X_LSB 0x40
#define FXOS8700_M_OFF_Y_MSB 0x41
#define FXOS8700_M_OFF_Y_LSB 0x42
#define FXOS8700_M_OFF_Z_MSB 0x43
#define FXOS8700_M_OFF_Z_LSB 0x44
#define FXOS8700_MAX_X_MSB 0x45
#define FXOS8700_MAX_X_LSB 0x46
#define FXOS8700_MAX_Y_MSB 0x47
#define FXOS8700_MAX_Y_LSB 0x48
#define FXOS8700_MAX_Z_MSB 0x49
#define FXOS8700_MAX_Z_LSB 0x4a
#define FXOS8700_MIN_X_MSB 0x4b
#define FXOS8700_MIN_X_LSB 0x4c
#define FXOS8700_MIN_Y_MSB 0x4d
#define FXOS8700_MIN_Y_LSB 0x4e
#define FXOS8700_MIN_Z_MSB 0x4f
#define FXOS8700_MIN_Z_LSB 0x50
#define FXOS8700_TEMP 0x51
#define FXOS8700_M_THS_CFG 0x52
#define FXOS8700_M_THS_SRC 0x53
#define FXOS8700_M_THS_X_MSB 0x54
#define FXOS8700_M_THS_X_LSB 0x55
#define FXOS8700_M_THS_Y_MSB 0x56
#define FXOS8700_M_THS_Y_LSB 0x57
#define FXOS8700_M_THS_Z_MSB 0x58
#define FXOS8700_M_THS_Z_LSB 0x59
#define FXOS8700_M_THS_COUNT 0x5a
#define FXOS8700_M_CTRL_REG1 0x5b
#define FXOS8700_M_CTRL_REG2 0x5c
#define FXOS8700_M_CTRL_REG3 0x5d
#define FXOS8700_M_INT_SRC 0x5e
#define FXOS8700_A_VECM_CFG 0x5f
#define FXOS8700_A_VECM_THS_MSB 0x60
#define FXOS8700_A_VECM_THS_LSB 0x61
#define FXOS8700_A_VECM_CNT 0x62
#define FXOS8700_A_VECM_INITX_MSB 0x63
#define FXOS8700_A_VECM_INITX_LSB 0x64
#define FXOS8700_A_VECM_INITY_MSB 0x65
#define FXOS8700_A_VECM_INITY_LSB 0x66
#define FXOS8700_A_VECM_INITZ_MSB 0x67
#define FXOS8700_A_VECM_INITZ_LSB 0x68
#define FXOS8700_M_VECM_CFG 0x69
#define FXOS8700_M_VECM_THS_MSB 0x6a
#define FXOS8700_M_VECM_THS_LSB 0x6b
#define FXOS8700_M_VECM_CNT 0x6c
#define FXOS8700_M_VECM_INITX_MSB 0x6d
#define FXOS8700_M_VECM_INITX_LSB 0x6e
#define FXOS8700_M_VECM_INITY_MSB 0x6f
#define FXOS8700_M_VECM_INITY_LSB 0x70
#define FXOS8700_M_VECM_INITZ_MSB 0x71
#define FXOS8700_M_VECM_INITZ_LSB 0x72
#define FXOS8700_A_FFMT_THS_X_MSB 0x73
#define FXOS8700_A_FFMT_THS_X_LSB 0x74
#define FXOS8700_A_FFMT_THS_Y_MSB 0x75
#define FXOS8700_A_FFMT_THS_Y_LSB 0x76
#define FXOS8700_A_FFMT_THS_Z_MSB 0x77
#define FXOS8700_A_FFMT_THS_Z_LSB 0x78
#define FXOS8700_A_TRAN_INIT_MSB 0x79
#define FXOS8700_A_TRAN_INIT_LSB_X 0x7a
#define FXOS8700_A_TRAN_INIT_LSB_Y 0x7b
#define FXOS8700_A_TRAN_INIT_LSB_Z 0x7d
#define FXOS8700_TM_NVM_LOCK 0x7e
#define FXOS8700_NVM_DATA0_35 0x80
#define FXOS8700_NVM_DATA_BNK3 0xa4
#define FXOS8700_NVM_DATA_BNK2 0xa5
#define FXOS8700_NVM_DATA_BNK1 0xa6
#define FXOS8700_NVM_DATA_BNK0 0xa7
/* Bit definitions for FXOS8700_CTRL_REG1 */
#define FXOS8700_CTRL_ODR_MSK 0x38
#define FXOS8700_CTRL_ODR_MAX 0x00
#define FXOS8700_CTRL_ODR_MIN GENMASK(4, 3)
/* Bit definitions for FXOS8700_M_CTRL_REG1 */
#define FXOS8700_HMS_MASK GENMASK(1, 0)
#define FXOS8700_OS_MASK GENMASK(4, 2)
/* Bit definitions for FXOS8700_M_CTRL_REG2 */
#define FXOS8700_MAXMIN_RST BIT(2)
#define FXOS8700_MAXMIN_DIS_THS BIT(3)
#define FXOS8700_MAXMIN_DIS BIT(4)
#define FXOS8700_ACTIVE 0x01
#define FXOS8700_ACTIVE_MIN_USLEEP 4000 /* from table 6 in datasheet */
#define FXOS8700_DEVICE_ID 0xC7
#define FXOS8700_PRE_DEVICE_ID 0xC4
#define FXOS8700_DATA_BUF_SIZE 3
struct fxos8700_data {
struct regmap *regmap;
struct iio_trigger *trig;
__be16 buf[FXOS8700_DATA_BUF_SIZE] ____cacheline_aligned;
};
/* Regmap info */
static const struct regmap_range read_range[] = {
{
.range_min = FXOS8700_STATUS,
.range_max = FXOS8700_A_FFMT_COUNT,
}, {
.range_min = FXOS8700_TRANSIENT_CFG,
.range_max = FXOS8700_A_FFMT_THS_Z_LSB,
},
};
static const struct regmap_range write_range[] = {
{
.range_min = FXOS8700_F_SETUP,
.range_max = FXOS8700_TRIG_CFG,
}, {
.range_min = FXOS8700_XYZ_DATA_CFG,
.range_max = FXOS8700_HP_FILTER_CUTOFF,
}, {
.range_min = FXOS8700_PL_CFG,
.range_max = FXOS8700_A_FFMT_CFG,
}, {
.range_min = FXOS8700_A_FFMT_THS,
.range_max = FXOS8700_TRANSIENT_CFG,
}, {
.range_min = FXOS8700_TRANSIENT_THS,
.range_max = FXOS8700_PULSE_CFG,
}, {
.range_min = FXOS8700_PULSE_THSX,
.range_max = FXOS8700_OFF_Z,
}, {
.range_min = FXOS8700_M_OFF_X_MSB,
.range_max = FXOS8700_M_OFF_Z_LSB,
}, {
.range_min = FXOS8700_M_THS_CFG,
.range_max = FXOS8700_M_THS_CFG,
}, {
.range_min = FXOS8700_M_THS_X_MSB,
.range_max = FXOS8700_M_CTRL_REG3,
}, {
.range_min = FXOS8700_A_VECM_CFG,
.range_max = FXOS8700_A_FFMT_THS_Z_LSB,
},
};
static const struct regmap_access_table driver_read_table = {
.yes_ranges = read_range,
.n_yes_ranges = ARRAY_SIZE(read_range),
};
static const struct regmap_access_table driver_write_table = {
.yes_ranges = write_range,
.n_yes_ranges = ARRAY_SIZE(write_range),
};
const struct regmap_config fxos8700_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = FXOS8700_NVM_DATA_BNK0,
.rd_table = &driver_read_table,
.wr_table = &driver_write_table,
};
EXPORT_SYMBOL(fxos8700_regmap_config);
#define FXOS8700_CHANNEL(_type, _axis) { \
.type = _type, \
.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_SAMP_FREQ), \
}
enum fxos8700_accel_scale_bits {
MODE_2G = 0,
MODE_4G,
MODE_8G,
};
/* scan indexes follow DATA register order */
enum fxos8700_scan_axis {
FXOS8700_SCAN_ACCEL_X = 0,
FXOS8700_SCAN_ACCEL_Y,
FXOS8700_SCAN_ACCEL_Z,
FXOS8700_SCAN_MAGN_X,
FXOS8700_SCAN_MAGN_Y,
FXOS8700_SCAN_MAGN_Z,
FXOS8700_SCAN_RHALL,
FXOS8700_SCAN_TIMESTAMP,
};
enum fxos8700_sensor {
FXOS8700_ACCEL = 0,
FXOS8700_MAGN,
FXOS8700_NUM_SENSORS /* must be last */
};
enum fxos8700_int_pin {
FXOS8700_PIN_INT1,
FXOS8700_PIN_INT2
};
struct fxos8700_scale {
u8 bits;
int uscale;
};
struct fxos8700_odr {
u8 bits;
int odr;
int uodr;
};
static const struct fxos8700_scale fxos8700_accel_scale[] = {
{ MODE_2G, 244},
{ MODE_4G, 488},
{ MODE_8G, 976},
};
/*
* Accellerometer and magnetometer have the same ODR options, set in the
* CTRL_REG1 register. ODR is halved when using both sensors at once in
* hybrid mode.
*/
static const struct fxos8700_odr fxos8700_odr[] = {
{0x00, 800, 0},
{0x01, 400, 0},
{0x02, 200, 0},
{0x03, 100, 0},
{0x04, 50, 0},
{0x05, 12, 500000},
{0x06, 6, 250000},
{0x07, 1, 562500},
};
static const struct iio_chan_spec fxos8700_channels[] = {
FXOS8700_CHANNEL(IIO_ACCEL, X),
FXOS8700_CHANNEL(IIO_ACCEL, Y),
FXOS8700_CHANNEL(IIO_ACCEL, Z),
FXOS8700_CHANNEL(IIO_MAGN, X),
FXOS8700_CHANNEL(IIO_MAGN, Y),
FXOS8700_CHANNEL(IIO_MAGN, Z),
IIO_CHAN_SOFT_TIMESTAMP(FXOS8700_SCAN_TIMESTAMP),
};
static enum fxos8700_sensor fxos8700_to_sensor(enum iio_chan_type iio_type)
{
switch (iio_type) {
case IIO_ACCEL:
return FXOS8700_ACCEL;
case IIO_ANGL_VEL:
return FXOS8700_MAGN;
default:
return -EINVAL;
}
}
static int fxos8700_set_active_mode(struct fxos8700_data *data,
enum fxos8700_sensor t, bool mode)
{
int ret;
ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, mode);
if (ret)
return ret;
usleep_range(FXOS8700_ACTIVE_MIN_USLEEP,
FXOS8700_ACTIVE_MIN_USLEEP + 1000);
return 0;
}
static int fxos8700_set_scale(struct fxos8700_data *data,
enum fxos8700_sensor t, int uscale)
{
int i;
static const int scale_num = ARRAY_SIZE(fxos8700_accel_scale);
struct device *dev = regmap_get_device(data->regmap);
if (t == FXOS8700_MAGN) {
dev_err(dev, "Magnetometer scale is locked at 1200uT\n");
return -EINVAL;
}
for (i = 0; i < scale_num; i++)
if (fxos8700_accel_scale[i].uscale == uscale)
break;
if (i == scale_num)
return -EINVAL;
return regmap_write(data->regmap, FXOS8700_XYZ_DATA_CFG,
fxos8700_accel_scale[i].bits);
}
static int fxos8700_get_scale(struct fxos8700_data *data,
enum fxos8700_sensor t, int *uscale)
{
int i, ret, val;
static const int scale_num = ARRAY_SIZE(fxos8700_accel_scale);
if (t == FXOS8700_MAGN) {
*uscale = 1200; /* Magnetometer is locked at 1200uT */
return 0;
}
ret = regmap_read(data->regmap, FXOS8700_XYZ_DATA_CFG, &val);
if (ret)
return ret;
for (i = 0; i < scale_num; i++) {
if (fxos8700_accel_scale[i].bits == (val & 0x3)) {
*uscale = fxos8700_accel_scale[i].uscale;
return 0;
}
}
return -EINVAL;
}
static int fxos8700_get_data(struct fxos8700_data *data, int chan_type,
int axis, int *val)
{
u8 base, reg;
int ret;
enum fxos8700_sensor type = fxos8700_to_sensor(chan_type);
base = type ? FXOS8700_OUT_X_MSB : FXOS8700_M_OUT_X_MSB;
/* Block read 6 bytes of device output registers to avoid data loss */
ret = regmap_bulk_read(data->regmap, base, data->buf,
FXOS8700_DATA_BUF_SIZE);
if (ret)
return ret;
/* Convert axis to buffer index */
reg = axis - IIO_MOD_X;
/* Convert to native endianness */
*val = sign_extend32(be16_to_cpu(data->buf[reg]), 15);
return 0;
}
static int fxos8700_set_odr(struct fxos8700_data *data, enum fxos8700_sensor t,
int odr, int uodr)
{
int i, ret, val;
bool active_mode;
static const int odr_num = ARRAY_SIZE(fxos8700_odr);
ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val);
if (ret)
return ret;
active_mode = val & FXOS8700_ACTIVE;
if (active_mode) {
/*
* The device must be in standby mode to change any of the
* other fields within CTRL_REG1
*/
ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1,
val & ~FXOS8700_ACTIVE);
if (ret)
return ret;
}
for (i = 0; i < odr_num; i++)
if (fxos8700_odr[i].odr == odr && fxos8700_odr[i].uodr == uodr)
break;
if (i >= odr_num)
return -EINVAL;
return regmap_update_bits(data->regmap,
FXOS8700_CTRL_REG1,
FXOS8700_CTRL_ODR_MSK + FXOS8700_ACTIVE,
fxos8700_odr[i].bits << 3 | active_mode);
}
static int fxos8700_get_odr(struct fxos8700_data *data, enum fxos8700_sensor t,
int *odr, int *uodr)
{
int i, val, ret;
static const int odr_num = ARRAY_SIZE(fxos8700_odr);
ret = regmap_read(data->regmap, FXOS8700_CTRL_REG1, &val);
if (ret)
return ret;
val &= FXOS8700_CTRL_ODR_MSK;
for (i = 0; i < odr_num; i++)
if (val == fxos8700_odr[i].bits)
break;
if (i >= odr_num)
return -EINVAL;
*odr = fxos8700_odr[i].odr;
*uodr = fxos8700_odr[i].uodr;
return 0;
}
static int fxos8700_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
int ret;
struct fxos8700_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = fxos8700_get_data(data, chan->type, chan->channel2, val);
if (ret)
return ret;
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = 0;
ret = fxos8700_get_scale(data, fxos8700_to_sensor(chan->type),
val2);
return ret ? ret : IIO_VAL_INT_PLUS_MICRO;
case IIO_CHAN_INFO_SAMP_FREQ:
ret = fxos8700_get_odr(data, fxos8700_to_sensor(chan->type),
val, val2);
return ret ? ret : IIO_VAL_INT_PLUS_MICRO;
default:
return -EINVAL;
}
}
static int fxos8700_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct fxos8700_data *data = iio_priv(indio_dev);
switch (mask) {
case IIO_CHAN_INFO_SCALE:
return fxos8700_set_scale(data, fxos8700_to_sensor(chan->type),
val2);
case IIO_CHAN_INFO_SAMP_FREQ:
return fxos8700_set_odr(data, fxos8700_to_sensor(chan->type),
val, val2);
default:
return -EINVAL;
}
}
static IIO_CONST_ATTR(in_accel_sampling_frequency_available,
"1.5625 6.25 12.5 50 100 200 400 800");
static IIO_CONST_ATTR(in_magn_sampling_frequency_available,
"1.5625 6.25 12.5 50 100 200 400 800");
static IIO_CONST_ATTR(in_accel_scale_available, "0.000244 0.000488 0.000976");
static IIO_CONST_ATTR(in_magn_scale_available, "0.000001200");
static struct attribute *fxos8700_attrs[] = {
&iio_const_attr_in_accel_sampling_frequency_available.dev_attr.attr,
&iio_const_attr_in_magn_sampling_frequency_available.dev_attr.attr,
&iio_const_attr_in_accel_scale_available.dev_attr.attr,
&iio_const_attr_in_magn_scale_available.dev_attr.attr,
NULL,
};
static const struct attribute_group fxos8700_attrs_group = {
.attrs = fxos8700_attrs,
};
static const struct iio_info fxos8700_info = {
.read_raw = fxos8700_read_raw,
.write_raw = fxos8700_write_raw,
.attrs = &fxos8700_attrs_group,
};
static int fxos8700_chip_init(struct fxos8700_data *data, bool use_spi)
{
int ret;
unsigned int val;
struct device *dev = regmap_get_device(data->regmap);
ret = regmap_read(data->regmap, FXOS8700_WHO_AM_I, &val);
if (ret) {
dev_err(dev, "Error reading chip id\n");
return ret;
}
if (val != FXOS8700_DEVICE_ID && val != FXOS8700_PRE_DEVICE_ID) {
dev_err(dev, "Wrong chip id, got %x expected %x or %x\n",
val, FXOS8700_DEVICE_ID, FXOS8700_PRE_DEVICE_ID);
return -ENODEV;
}
ret = fxos8700_set_active_mode(data, FXOS8700_ACCEL, true);
if (ret)
return ret;
ret = fxos8700_set_active_mode(data, FXOS8700_MAGN, true);
if (ret)
return ret;
/*
* The device must be in standby mode to change any of the other fields
* within CTRL_REG1
*/
ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1, 0x00);
if (ret)
return ret;
/* Set max oversample ratio (OSR) and both devices active */
ret = regmap_write(data->regmap, FXOS8700_M_CTRL_REG1,
FXOS8700_HMS_MASK | FXOS8700_OS_MASK);
if (ret)
return ret;
/* Disable and rst min/max measurements & threshold */
ret = regmap_write(data->regmap, FXOS8700_M_CTRL_REG2,
FXOS8700_MAXMIN_RST | FXOS8700_MAXMIN_DIS_THS |
FXOS8700_MAXMIN_DIS);
if (ret)
return ret;
/* Max ODR (800Hz individual or 400Hz hybrid), active mode */
ret = regmap_write(data->regmap, FXOS8700_CTRL_REG1,
FXOS8700_CTRL_ODR_MAX | FXOS8700_ACTIVE);
if (ret)
return ret;
/* Set for max full-scale range (+/-8G) */
return regmap_write(data->regmap, FXOS8700_XYZ_DATA_CFG, MODE_8G);
}
static void fxos8700_chip_uninit(void *data)
{
struct fxos8700_data *fxos8700_data = data;
fxos8700_set_active_mode(fxos8700_data, FXOS8700_ACCEL, false);
fxos8700_set_active_mode(fxos8700_data, FXOS8700_MAGN, false);
}
int fxos8700_core_probe(struct device *dev, struct regmap *regmap,
const char *name, bool use_spi)
{
struct iio_dev *indio_dev;
struct fxos8700_data *data;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
data = iio_priv(indio_dev);
dev_set_drvdata(dev, indio_dev);
data->regmap = regmap;
ret = fxos8700_chip_init(data, use_spi);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, fxos8700_chip_uninit, data);
if (ret)
return ret;
indio_dev->channels = fxos8700_channels;
indio_dev->num_channels = ARRAY_SIZE(fxos8700_channels);
indio_dev->name = name ? name : "fxos8700";
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->info = &fxos8700_info;
return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_GPL(fxos8700_core_probe);
MODULE_AUTHOR("Robert Jones <rjones@gateworks.com>");
MODULE_DESCRIPTION("FXOS8700 6-Axis Acc and Mag Combo Sensor driver");
MODULE_LICENSE("GPL v2");