diff options
Diffstat (limited to 'drivers/iio/proximity/sx9310.c')
-rw-r--r-- | drivers/iio/proximity/sx9310.c | 508 |
1 files changed, 502 insertions, 6 deletions
diff --git a/drivers/iio/proximity/sx9310.c b/drivers/iio/proximity/sx9310.c index 6d3f4ab8c6b2..a2f820997afc 100644 --- a/drivers/iio/proximity/sx9310.c +++ b/drivers/iio/proximity/sx9310.c @@ -16,6 +16,7 @@ #include <linux/i2c.h> #include <linux/irq.h> #include <linux/kernel.h> +#include <linux/log2.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/pm.h> @@ -48,30 +49,55 @@ #define SX9310_REG_PROX_CTRL0_SCANPERIOD_15MS 0x01 #define SX9310_REG_PROX_CTRL1 0x11 #define SX9310_REG_PROX_CTRL2 0x12 +#define SX9310_REG_PROX_CTRL2_COMBMODE_MASK GENMASK(7, 6) +#define SX9310_REG_PROX_CTRL2_COMBMODE_CS0_CS1_CS2_CS3 (0x03 << 6) #define SX9310_REG_PROX_CTRL2_COMBMODE_CS1_CS2 (0x02 << 6) +#define SX9310_REG_PROX_CTRL2_COMBMODE_CS0_CS1 (0x01 << 6) +#define SX9310_REG_PROX_CTRL2_COMBMODE_CS3 (0x00 << 6) +#define SX9310_REG_PROX_CTRL2_SHIELDEN_MASK GENMASK(3, 2) #define SX9310_REG_PROX_CTRL2_SHIELDEN_DYNAMIC (0x01 << 2) +#define SX9310_REG_PROX_CTRL2_SHIELDEN_GROUND (0x02 << 2) #define SX9310_REG_PROX_CTRL3 0x13 +#define SX9310_REG_PROX_CTRL3_GAIN0_MASK GENMASK(3, 2) #define SX9310_REG_PROX_CTRL3_GAIN0_X8 (0x03 << 2) +#define SX9310_REG_PROX_CTRL3_GAIN12_MASK GENMASK(1, 0) #define SX9310_REG_PROX_CTRL3_GAIN12_X4 0x02 #define SX9310_REG_PROX_CTRL4 0x14 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_MASK GENMASK(2, 0) #define SX9310_REG_PROX_CTRL4_RESOLUTION_FINEST 0x07 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_VERY_FINE 0x06 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_FINE 0x05 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_MEDIUM 0x04 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_MEDIUM_COARSE 0x03 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_COARSE 0x02 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_VERY_COARSE 0x01 +#define SX9310_REG_PROX_CTRL4_RESOLUTION_COARSEST 0x00 #define SX9310_REG_PROX_CTRL5 0x15 #define SX9310_REG_PROX_CTRL5_RANGE_SMALL (0x03 << 6) +#define SX9310_REG_PROX_CTRL5_STARTUPSENS_MASK GENMASK(3, 2) #define SX9310_REG_PROX_CTRL5_STARTUPSENS_CS1 (0x01 << 2) +#define SX9310_REG_PROX_CTRL5_RAWFILT_MASK GENMASK(1, 0) +#define SX9310_REG_PROX_CTRL5_RAWFILT_SHIFT 0 #define SX9310_REG_PROX_CTRL5_RAWFILT_1P25 0x02 #define SX9310_REG_PROX_CTRL6 0x16 #define SX9310_REG_PROX_CTRL6_AVGTHRESH_DEFAULT 0x20 #define SX9310_REG_PROX_CTRL7 0x17 #define SX9310_REG_PROX_CTRL7_AVGNEGFILT_2 (0x01 << 3) +#define SX9310_REG_PROX_CTRL7_AVGPOSFILT_MASK GENMASK(2, 0) +#define SX9310_REG_PROX_CTRL7_AVGPOSFILT_SHIFT 0 #define SX9310_REG_PROX_CTRL7_AVGPOSFILT_512 0x05 #define SX9310_REG_PROX_CTRL8 0x18 +#define SX9310_REG_PROX_CTRL8_9_PTHRESH_MASK GENMASK(7, 3) #define SX9310_REG_PROX_CTRL9 0x19 #define SX9310_REG_PROX_CTRL8_9_PTHRESH_28 (0x08 << 3) #define SX9310_REG_PROX_CTRL8_9_PTHRESH_96 (0x11 << 3) #define SX9310_REG_PROX_CTRL8_9_BODYTHRESH_900 0x03 #define SX9310_REG_PROX_CTRL8_9_BODYTHRESH_1500 0x05 #define SX9310_REG_PROX_CTRL10 0x1a +#define SX9310_REG_PROX_CTRL10_HYST_MASK GENMASK(5, 4) #define SX9310_REG_PROX_CTRL10_HYST_6PCT (0x01 << 4) +#define SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK GENMASK(3, 2) +#define SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK GENMASK(1, 0) #define SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_2 0x01 #define SX9310_REG_PROX_CTRL11 0x1b #define SX9310_REG_PROX_CTRL12 0x1c @@ -144,16 +170,31 @@ struct sx9310_data { static const struct iio_event_spec sx9310_events[] = { { .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_all = BIT(IIO_EV_INFO_PERIOD), + }, + { + .type = IIO_EV_TYPE_THRESH, .dir = IIO_EV_DIR_EITHER, - .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_VALUE), }, }; #define SX9310_NAMED_CHANNEL(idx, name) \ { \ .type = IIO_PROXIMITY, \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate_available = \ + BIT(IIO_CHAN_INFO_HARDWAREGAIN), \ .indexed = 1, \ .channel = idx, \ .extend_name = name, \ @@ -426,6 +467,34 @@ out: return ret; } +static int sx9310_read_gain(struct sx9310_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int regval, gain; + int ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL3, ®val); + if (ret) + return ret; + + switch (chan->channel) { + case 0: + case 3: + gain = FIELD_GET(SX9310_REG_PROX_CTRL3_GAIN0_MASK, regval); + break; + case 1: + case 2: + gain = FIELD_GET(SX9310_REG_PROX_CTRL3_GAIN12_MASK, regval); + break; + default: + return -EINVAL; + } + + *val = 1 << gain; + + return IIO_VAL_INT; +} + static int sx9310_read_samp_freq(struct sx9310_data *data, int *val, int *val2) { unsigned int regval; @@ -461,6 +530,14 @@ static int sx9310_read_raw(struct iio_dev *indio_dev, ret = sx9310_read_proximity(data, chan, val); iio_device_release_direct_mode(indio_dev); return ret; + case IIO_CHAN_INFO_HARDWAREGAIN: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = sx9310_read_gain(data, chan, val); + iio_device_release_direct_mode(indio_dev); + return ret; case IIO_CHAN_INFO_SAMP_FREQ: return sx9310_read_samp_freq(data, val, val2); default: @@ -468,6 +545,283 @@ static int sx9310_read_raw(struct iio_dev *indio_dev, } } +static const int sx9310_gain_vals[] = { 1, 2, 4, 8 }; + +static int sx9310_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(sx9310_gain_vals); + *vals = sx9310_gain_vals; + return IIO_AVAIL_LIST; + } + + return -EINVAL; +} + +static const unsigned int sx9310_pthresh_codes[] = { + 2, 4, 6, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96, 112, + 128, 144, 160, 192, 224, 256, 320, 384, 512, 640, 768, 1024, 1536 +}; + +static int sx9310_get_thresh_reg(unsigned int channel) +{ + switch (channel) { + case 0: + case 3: + return SX9310_REG_PROX_CTRL8; + case 1: + case 2: + return SX9310_REG_PROX_CTRL9; + } + + return -EINVAL; +} + +static int sx9310_read_thresh(struct sx9310_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int reg; + unsigned int regval; + int ret; + + reg = ret = sx9310_get_thresh_reg(chan->channel); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL8_9_PTHRESH_MASK, regval); + if (regval > ARRAY_SIZE(sx9310_pthresh_codes)) + return -EINVAL; + + *val = sx9310_pthresh_codes[regval]; + return IIO_VAL_INT; +} + +static int sx9310_read_hysteresis(struct sx9310_data *data, + const struct iio_chan_spec *chan, int *val) +{ + unsigned int regval, pthresh; + int ret; + + ret = sx9310_read_thresh(data, chan, &pthresh); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL10, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL10_HYST_MASK, regval); + if (!regval) + regval = 5; + + /* regval is at most 5 */ + *val = pthresh >> (5 - regval); + + return IIO_VAL_INT; +} + +static int sx9310_read_far_debounce(struct sx9310_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL10, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, regval); + if (regval) + *val = 1 << regval; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int sx9310_read_close_debounce(struct sx9310_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, SX9310_REG_PROX_CTRL10, ®val); + if (ret) + return ret; + + regval = FIELD_GET(SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, regval); + if (regval) + *val = 1 << regval; + else + *val = 0; + + return IIO_VAL_INT; +} + +static int sx9310_read_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, int *val2) +{ + struct sx9310_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return sx9310_read_thresh(data, chan, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return sx9310_read_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return sx9310_read_close_debounce(data, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return sx9310_read_hysteresis(data, chan, val); + default: + return -EINVAL; + } +} + +static int sx9310_write_thresh(struct sx9310_data *data, + const struct iio_chan_spec *chan, int val) +{ + unsigned int reg; + unsigned int regval; + int ret, i; + + reg = ret = sx9310_get_thresh_reg(chan->channel); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(sx9310_pthresh_codes); i++) { + if (sx9310_pthresh_codes[i] == val) { + regval = i; + break; + } + } + + if (i == ARRAY_SIZE(sx9310_pthresh_codes)) + return -EINVAL; + + regval = FIELD_PREP(SX9310_REG_PROX_CTRL8_9_PTHRESH_MASK, regval); + mutex_lock(&data->mutex); + ret = regmap_update_bits(data->regmap, reg, + SX9310_REG_PROX_CTRL8_9_PTHRESH_MASK, regval); + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx9310_write_hysteresis(struct sx9310_data *data, + const struct iio_chan_spec *chan, int _val) +{ + unsigned int hyst, val = _val; + int ret, pthresh; + + ret = sx9310_read_thresh(data, chan, &pthresh); + if (ret < 0) + return ret; + + if (val == 0) + hyst = 0; + else if (val == pthresh >> 2) + hyst = 3; + else if (val == pthresh >> 3) + hyst = 2; + else if (val == pthresh >> 4) + hyst = 1; + else + return -EINVAL; + + hyst = FIELD_PREP(SX9310_REG_PROX_CTRL10_HYST_MASK, hyst); + mutex_lock(&data->mutex); + ret = regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL10, + SX9310_REG_PROX_CTRL10_HYST_MASK, hyst); + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx9310_write_far_debounce(struct sx9310_data *data, int val) +{ + int ret; + unsigned int regval; + + val = ilog2(val); + regval = FIELD_PREP(SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, val); + + mutex_lock(&data->mutex); + ret = regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL10, + SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, + regval); + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx9310_write_close_debounce(struct sx9310_data *data, int val) +{ + int ret; + unsigned int regval; + + val = ilog2(val); + regval = FIELD_PREP(SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, val); + + mutex_lock(&data->mutex); + ret = regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL10, + SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, + regval); + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx9310_write_event_val(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, int val2) +{ + struct sx9310_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + return sx9310_write_thresh(data, chan, val); + case IIO_EV_INFO_PERIOD: + switch (dir) { + case IIO_EV_DIR_RISING: + return sx9310_write_far_debounce(data, val); + case IIO_EV_DIR_FALLING: + return sx9310_write_close_debounce(data, val); + default: + return -EINVAL; + } + case IIO_EV_INFO_HYSTERESIS: + return sx9310_write_hysteresis(data, chan, val); + default: + return -EINVAL; + } +} + static int sx9310_set_samp_freq(struct sx9310_data *data, int val, int val2) { int i, ret; @@ -492,6 +846,37 @@ static int sx9310_set_samp_freq(struct sx9310_data *data, int val, int val2) return ret; } +static int sx9310_write_gain(struct sx9310_data *data, + const struct iio_chan_spec *chan, int val) +{ + unsigned int gain, mask; + int ret; + + gain = ilog2(val); + + switch (chan->channel) { + case 0: + case 3: + mask = SX9310_REG_PROX_CTRL3_GAIN0_MASK; + gain = FIELD_PREP(SX9310_REG_PROX_CTRL3_GAIN0_MASK, gain); + break; + case 1: + case 2: + mask = SX9310_REG_PROX_CTRL3_GAIN12_MASK; + gain = FIELD_PREP(SX9310_REG_PROX_CTRL3_GAIN12_MASK, gain); + break; + default: + return -EINVAL; + } + + mutex_lock(&data->mutex); + ret = regmap_update_bits(data->regmap, SX9310_REG_PROX_CTRL3, mask, + gain); + mutex_unlock(&data->mutex); + + return ret; +} + static int sx9310_write_raw(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, int val, int val2, long mask) @@ -501,10 +886,14 @@ static int sx9310_write_raw(struct iio_dev *indio_dev, if (chan->type != IIO_PROXIMITY) return -EINVAL; - if (mask != IIO_CHAN_INFO_SAMP_FREQ) - return -EINVAL; + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return sx9310_set_samp_freq(data, val, val2); + case IIO_CHAN_INFO_HARDWAREGAIN: + return sx9310_write_gain(data, chan, val); + } - return sx9310_set_samp_freq(data, val, val2); + return -EINVAL; } static irqreturn_t sx9310_irq_handler(int irq, void *private) @@ -645,6 +1034,9 @@ static const struct attribute_group sx9310_attribute_group = { static const struct iio_info sx9310_info = { .attrs = &sx9310_attribute_group, .read_raw = sx9310_read_raw, + .read_avail = sx9310_read_avail, + .read_event_value = sx9310_read_event_val, + .write_event_value = sx9310_write_event_val, .write_raw = sx9310_write_raw, .read_event_config = sx9310_read_event_config, .write_event_config = sx9310_write_event_config, @@ -820,9 +1212,113 @@ static int sx9310_init_compensation(struct iio_dev *indio_dev) return ret; } +static const struct sx9310_reg_default * +sx9310_get_default_reg(struct sx9310_data *data, int i, + struct sx9310_reg_default *reg_def) +{ + int ret; + const struct device_node *np = data->client->dev.of_node; + u32 combined[SX9310_NUM_CHANNELS] = { 4, 4, 4, 4 }; + unsigned long comb_mask = 0; + const char *res; + u32 start = 0, raw = 0, pos = 0; + + memcpy(reg_def, &sx9310_default_regs[i], sizeof(*reg_def)); + if (!np) + return reg_def; + + switch (reg_def->reg) { + case SX9310_REG_PROX_CTRL2: + if (of_property_read_bool(np, "semtech,cs0-ground")) { + reg_def->def &= ~SX9310_REG_PROX_CTRL2_SHIELDEN_MASK; + reg_def->def |= SX9310_REG_PROX_CTRL2_SHIELDEN_GROUND; + } + + reg_def->def &= ~SX9310_REG_PROX_CTRL2_COMBMODE_MASK; + of_property_read_u32_array(np, "semtech,combined-sensors", + combined, ARRAY_SIZE(combined)); + for (i = 0; i < ARRAY_SIZE(combined); i++) { + if (combined[i] <= SX9310_NUM_CHANNELS) + comb_mask |= BIT(combined[i]); + } + + comb_mask &= 0xf; + if (comb_mask == (BIT(3) | BIT(2) | BIT(1) | BIT(0))) + reg_def->def |= SX9310_REG_PROX_CTRL2_COMBMODE_CS0_CS1_CS2_CS3; + else if (comb_mask == (BIT(1) | BIT(2))) + reg_def->def |= SX9310_REG_PROX_CTRL2_COMBMODE_CS1_CS2; + else if (comb_mask == (BIT(0) | BIT(1))) + reg_def->def |= SX9310_REG_PROX_CTRL2_COMBMODE_CS0_CS1; + else if (comb_mask == BIT(3)) + reg_def->def |= SX9310_REG_PROX_CTRL2_COMBMODE_CS3; + + break; + case SX9310_REG_PROX_CTRL4: + ret = of_property_read_string(np, "semtech,resolution", &res); + if (ret) + break; + + reg_def->def &= ~SX9310_REG_PROX_CTRL4_RESOLUTION_MASK; + if (!strcmp(res, "coarsest")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_COARSEST; + else if (!strcmp(res, "very-coarse")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_VERY_COARSE; + else if (!strcmp(res, "coarse")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_COARSE; + else if (!strcmp(res, "medium-coarse")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_MEDIUM_COARSE; + else if (!strcmp(res, "medium")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_MEDIUM; + else if (!strcmp(res, "fine")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_FINE; + else if (!strcmp(res, "very-fine")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_VERY_FINE; + else if (!strcmp(res, "finest")) + reg_def->def |= SX9310_REG_PROX_CTRL4_RESOLUTION_FINEST; + + break; + case SX9310_REG_PROX_CTRL5: + ret = of_property_read_u32(np, "semtech,startup-sensor", &start); + if (ret) { + start = FIELD_GET(SX9310_REG_PROX_CTRL5_STARTUPSENS_MASK, + reg_def->def); + } + + reg_def->def &= ~SX9310_REG_PROX_CTRL5_STARTUPSENS_MASK; + reg_def->def |= FIELD_PREP(SX9310_REG_PROX_CTRL5_STARTUPSENS_MASK, + start); + + ret = of_property_read_u32(np, "semtech,proxraw-strength", &raw); + if (ret) { + raw = FIELD_GET(SX9310_REG_PROX_CTRL5_RAWFILT_MASK, + reg_def->def); + } else { + raw = ilog2(raw); + } + + reg_def->def &= ~SX9310_REG_PROX_CTRL5_RAWFILT_MASK; + reg_def->def |= FIELD_PREP(SX9310_REG_PROX_CTRL5_RAWFILT_MASK, + raw); + break; + case SX9310_REG_PROX_CTRL7: + ret = of_property_read_u32(np, "semtech,avg-pos-strength", &pos); + if (ret) + break; + + pos = min(max(ilog2(pos), 3), 10) - 3; + reg_def->def &= ~SX9310_REG_PROX_CTRL7_AVGPOSFILT_MASK; + reg_def->def |= FIELD_PREP(SX9310_REG_PROX_CTRL7_AVGPOSFILT_MASK, + pos); + break; + } + + return reg_def; +} + static int sx9310_init_device(struct iio_dev *indio_dev) { struct sx9310_data *data = iio_priv(indio_dev); + struct sx9310_reg_default tmp; const struct sx9310_reg_default *initval; int ret; unsigned int i, val; @@ -840,7 +1336,7 @@ static int sx9310_init_device(struct iio_dev *indio_dev) /* Program some sane defaults. */ for (i = 0; i < ARRAY_SIZE(sx9310_default_regs); i++) { - initval = &sx9310_default_regs[i]; + initval = sx9310_get_default_reg(data, i, &tmp); ret = regmap_write(data->regmap, initval->reg, initval->def); if (ret) return ret; |