diff options
Diffstat (limited to 'drivers/iio/proximity')
-rw-r--r-- | drivers/iio/proximity/Kconfig | 11 | ||||
-rw-r--r-- | drivers/iio/proximity/Makefile | 1 | ||||
-rw-r--r-- | drivers/iio/proximity/as3935.c | 1 | ||||
-rw-r--r-- | drivers/iio/proximity/cros_ec_mkbp_proximity.c | 271 | ||||
-rw-r--r-- | drivers/iio/proximity/pulsedlight-lidar-lite-v2.c | 1 | ||||
-rw-r--r-- | drivers/iio/proximity/sx9310.c | 53 | ||||
-rw-r--r-- | drivers/iio/proximity/sx9500.c | 3 | ||||
-rw-r--r-- | drivers/iio/proximity/vcnl3020.c | 97 |
8 files changed, 419 insertions, 19 deletions
diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig index 12672a0e89ed..7c7203ca3ac6 100644 --- a/drivers/iio/proximity/Kconfig +++ b/drivers/iio/proximity/Kconfig @@ -21,6 +21,17 @@ endmenu menu "Proximity and distance sensors" +config CROS_EC_MKBP_PROXIMITY + tristate "ChromeOS EC MKBP Proximity sensor" + depends on CROS_EC + help + Say Y here to enable the proximity sensor implemented via the ChromeOS EC MKBP + switches protocol. You must enable one bus option (CROS_EC_I2C or CROS_EC_SPI) + to use this. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_mkbp_proximity. + config ISL29501 tristate "Intersil ISL29501 Time Of Flight sensor" depends on I2C diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile index 9c1aca1a8b79..cbdac09433eb 100644 --- a/drivers/iio/proximity/Makefile +++ b/drivers/iio/proximity/Makefile @@ -5,6 +5,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AS3935) += as3935.o +obj-$(CONFIG_CROS_EC_MKBP_PROXIMITY) += cros_ec_mkbp_proximity.o obj-$(CONFIG_ISL29501) += isl29501.o obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o obj-$(CONFIG_MB1232) += mb1232.o diff --git a/drivers/iio/proximity/as3935.c b/drivers/iio/proximity/as3935.c index b79ada839e01..edc4a35ae66d 100644 --- a/drivers/iio/proximity/as3935.c +++ b/drivers/iio/proximity/as3935.c @@ -411,7 +411,6 @@ static int as3935_probe(struct spi_device *spi) st->trig = trig; st->noise_tripped = jiffies - HZ; - trig->dev.parent = indio_dev->dev.parent; iio_trigger_set_drvdata(trig, indio_dev); trig->ops = &iio_interrupt_trigger_ops; diff --git a/drivers/iio/proximity/cros_ec_mkbp_proximity.c b/drivers/iio/proximity/cros_ec_mkbp_proximity.c new file mode 100644 index 000000000000..8213b0081713 --- /dev/null +++ b/drivers/iio/proximity/cros_ec_mkbp_proximity.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for cros-ec proximity sensor exposed through MKBP switch + * + * Copyright 2021 Google LLC. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> + +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> + +#include <linux/iio/events.h> +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#include <asm/unaligned.h> + +struct cros_ec_mkbp_proximity_data { + struct cros_ec_device *ec; + struct iio_dev *indio_dev; + struct mutex lock; + struct notifier_block notifier; + int last_proximity; + bool enabled; +}; + +static const struct iio_event_spec cros_ec_mkbp_proximity_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +static const struct iio_chan_spec cros_ec_mkbp_proximity_chan_spec[] = { + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = cros_ec_mkbp_proximity_events, + .num_event_specs = ARRAY_SIZE(cros_ec_mkbp_proximity_events), + }, +}; + +static int cros_ec_mkbp_proximity_parse_state(const void *data) +{ + u32 switches = get_unaligned_le32(data); + + return !!(switches & BIT(EC_MKBP_FRONT_PROXIMITY)); +} + +static int cros_ec_mkbp_proximity_query(struct cros_ec_device *ec_dev, + int *state) +{ + struct { + struct cros_ec_command msg; + union { + struct ec_params_mkbp_info params; + u32 switches; + }; + } __packed buf = { }; + struct ec_params_mkbp_info *params = &buf.params; + struct cros_ec_command *msg = &buf.msg; + u32 *switches = &buf.switches; + size_t insize = sizeof(*switches); + int ret; + + msg->command = EC_CMD_MKBP_INFO; + msg->version = 1; + msg->outsize = sizeof(*params); + msg->insize = insize; + + params->info_type = EC_MKBP_INFO_CURRENT; + params->event_type = EC_MKBP_EVENT_SWITCH; + + ret = cros_ec_cmd_xfer_status(ec_dev, msg); + if (ret < 0) + return ret; + + if (ret != insize) { + dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n", ret, + insize); + return -EPROTO; + } + + *state = cros_ec_mkbp_proximity_parse_state(switches); + return IIO_VAL_INT; +} + +static void cros_ec_mkbp_proximity_push_event(struct cros_ec_mkbp_proximity_data *data, int state) +{ + s64 timestamp; + u64 ev; + int dir; + struct iio_dev *indio_dev = data->indio_dev; + struct cros_ec_device *ec = data->ec; + + mutex_lock(&data->lock); + if (state != data->last_proximity) { + if (data->enabled) { + timestamp = ktime_to_ns(ec->last_event_time); + if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME) + timestamp = iio_get_time_ns(indio_dev); + + dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, + IIO_EV_TYPE_THRESH, dir); + iio_push_event(indio_dev, ev, timestamp); + } + data->last_proximity = state; + } + mutex_unlock(&data->lock); +} + +static int cros_ec_mkbp_proximity_notify(struct notifier_block *nb, + unsigned long queued_during_suspend, + void *_ec) +{ + struct cros_ec_mkbp_proximity_data *data; + struct cros_ec_device *ec = _ec; + u8 event_type = ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK; + void *switches; + int state; + + if (event_type == EC_MKBP_EVENT_SWITCH) { + data = container_of(nb, struct cros_ec_mkbp_proximity_data, + notifier); + + switches = &ec->event_data.data.switches; + state = cros_ec_mkbp_proximity_parse_state(switches); + cros_ec_mkbp_proximity_push_event(data, state); + } + + return NOTIFY_OK; +} + +static int cros_ec_mkbp_proximity_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long mask) +{ + struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); + struct cros_ec_device *ec = data->ec; + + if (chan->type == IIO_PROXIMITY && mask == IIO_CHAN_INFO_RAW) + return cros_ec_mkbp_proximity_query(ec, val); + + return -EINVAL; +} + +static int cros_ec_mkbp_proximity_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); + + return data->enabled; +} + +static int cros_ec_mkbp_proximity_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); + + mutex_lock(&data->lock); + data->enabled = state; + mutex_unlock(&data->lock); + + return 0; +} + +static const struct iio_info cros_ec_mkbp_proximity_info = { + .read_raw = cros_ec_mkbp_proximity_read_raw, + .read_event_config = cros_ec_mkbp_proximity_read_event_config, + .write_event_config = cros_ec_mkbp_proximity_write_event_config, +}; + +static __maybe_unused int cros_ec_mkbp_proximity_resume(struct device *dev) +{ + struct cros_ec_mkbp_proximity_data *data = dev_get_drvdata(dev); + struct cros_ec_device *ec = data->ec; + int ret, state; + + ret = cros_ec_mkbp_proximity_query(ec, &state); + if (ret < 0) { + dev_warn(dev, "failed to fetch proximity state on resume: %d\n", + ret); + } else { + cros_ec_mkbp_proximity_push_event(data, state); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(cros_ec_mkbp_proximity_pm_ops, NULL, + cros_ec_mkbp_proximity_resume); + +static int cros_ec_mkbp_proximity_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_device *ec = dev_get_drvdata(dev->parent); + struct iio_dev *indio_dev; + struct cros_ec_mkbp_proximity_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->ec = ec; + data->indio_dev = indio_dev; + data->last_proximity = -1; /* Unknown to start */ + mutex_init(&data->lock); + platform_set_drvdata(pdev, data); + + indio_dev->name = dev->driver->name; + indio_dev->info = &cros_ec_mkbp_proximity_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = cros_ec_mkbp_proximity_chan_spec; + indio_dev->num_channels = ARRAY_SIZE(cros_ec_mkbp_proximity_chan_spec); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + data->notifier.notifier_call = cros_ec_mkbp_proximity_notify; + blocking_notifier_chain_register(&ec->event_notifier, &data->notifier); + + return 0; +} + +static int cros_ec_mkbp_proximity_remove(struct platform_device *pdev) +{ + struct cros_ec_mkbp_proximity_data *data = platform_get_drvdata(pdev); + struct cros_ec_device *ec = data->ec; + + blocking_notifier_chain_unregister(&ec->event_notifier, + &data->notifier); + + return 0; +} + +static const struct of_device_id cros_ec_mkbp_proximity_of_match[] = { + { .compatible = "google,cros-ec-mkbp-proximity" }, + {} +}; +MODULE_DEVICE_TABLE(of, cros_ec_mkbp_proximity_of_match); + +static struct platform_driver cros_ec_mkbp_proximity_driver = { + .driver = { + .name = "cros-ec-mkbp-proximity", + .of_match_table = cros_ec_mkbp_proximity_of_match, + .pm = &cros_ec_mkbp_proximity_pm_ops, + }, + .probe = cros_ec_mkbp_proximity_probe, + .remove = cros_ec_mkbp_proximity_remove, +}; +module_platform_driver(cros_ec_mkbp_proximity_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ChromeOS EC MKBP proximity sensor driver"); diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index c685f10b5ae4..cc206bfa09c7 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -160,6 +160,7 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg) ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE); if (ret < 0) { dev_err(&client->dev, "cannot send start measurement command"); + pm_runtime_put_noidle(&client->dev); return ret; } diff --git a/drivers/iio/proximity/sx9310.c b/drivers/iio/proximity/sx9310.c index 37fd0b65a014..327ebb7ddbb9 100644 --- a/drivers/iio/proximity/sx9310.c +++ b/drivers/iio/proximity/sx9310.c @@ -763,7 +763,11 @@ static int sx9310_write_far_debounce(struct sx9310_data *data, int val) int ret; unsigned int regval; - val = ilog2(val); + if (val > 0) + val = ilog2(val); + if (!FIELD_FIT(SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, val)) + return -EINVAL; + regval = FIELD_PREP(SX9310_REG_PROX_CTRL10_FAR_DEBOUNCE_MASK, val); mutex_lock(&data->mutex); @@ -780,7 +784,11 @@ static int sx9310_write_close_debounce(struct sx9310_data *data, int val) int ret; unsigned int regval; - val = ilog2(val); + if (val > 0) + val = ilog2(val); + if (!FIELD_FIT(SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, val)) + return -EINVAL; + regval = FIELD_PREP(SX9310_REG_PROX_CTRL10_CLOSE_DEBOUNCE_MASK, val); mutex_lock(&data->mutex); @@ -1213,17 +1221,17 @@ static int sx9310_init_compensation(struct iio_dev *indio_dev) } static const struct sx9310_reg_default * -sx9310_get_default_reg(struct sx9310_data *data, int i, +sx9310_get_default_reg(struct sx9310_data *data, int idx, 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 }; + u32 combined[SX9310_NUM_CHANNELS]; + u32 start = 0, raw = 0, pos = 0; unsigned long comb_mask = 0; + int ret, i, count; const char *res; - u32 start = 0, raw = 0, pos = 0; - memcpy(reg_def, &sx9310_default_regs[i], sizeof(*reg_def)); + memcpy(reg_def, &sx9310_default_regs[idx], sizeof(*reg_def)); if (!np) return reg_def; @@ -1234,15 +1242,31 @@ sx9310_get_default_reg(struct sx9310_data *data, int i, 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]); + count = of_property_count_elems_of_size(np, "semtech,combined-sensors", + sizeof(u32)); + if (count > 0 && count <= ARRAY_SIZE(combined)) { + ret = of_property_read_u32_array(np, "semtech,combined-sensors", + combined, count); + if (ret) + break; + } else { + /* + * Either the property does not exist in the DT or the + * number of entries is incorrect. + */ + break; } + for (i = 0; i < count; i++) { + if (combined[i] >= SX9310_NUM_CHANNELS) { + /* Invalid sensor (invalid DT). */ + break; + } + comb_mask |= BIT(combined[i]); + } + if (i < count) + break; - comb_mask &= 0xf; + reg_def->def &= ~SX9310_REG_PROX_CTRL2_COMBMODE_MASK; 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))) @@ -1453,7 +1477,6 @@ static int sx9310_probe(struct i2c_client *client) if (!data->trig) return -ENOMEM; - data->trig->dev.parent = dev; data->trig->ops = &sx9310_trigger_ops; iio_trigger_set_drvdata(data->trig, indio_dev); diff --git a/drivers/iio/proximity/sx9500.c b/drivers/iio/proximity/sx9500.c index acb821cbad46..a87f4a8e4327 100644 --- a/drivers/iio/proximity/sx9500.c +++ b/drivers/iio/proximity/sx9500.c @@ -758,7 +758,7 @@ static const struct sx9500_reg_default sx9500_default_regs[] = { .reg = SX9500_REG_PROX_CTRL5, /* * Debouncer off, lowest average negative filter, - * highest average postive filter. + * highest average positive filter. */ .def = 0x0f, }, @@ -950,7 +950,6 @@ static int sx9500_probe(struct i2c_client *client, if (!data->trig) return -ENOMEM; - data->trig->dev.parent = &client->dev; data->trig->ops = &sx9500_trigger_ops; iio_trigger_set_drvdata(data->trig, indio_dev); diff --git a/drivers/iio/proximity/vcnl3020.c b/drivers/iio/proximity/vcnl3020.c index 37264f801ad0..43817f6b3086 100644 --- a/drivers/iio/proximity/vcnl3020.c +++ b/drivers/iio/proximity/vcnl3020.c @@ -40,6 +40,17 @@ #define VCNL_ON_DEMAND_TIMEOUT_US 100000 #define VCNL_POLL_US 20000 +static const int vcnl3020_prox_sampling_frequency[][2] = { + {1, 950000}, + {3, 906250}, + {7, 812500}, + {16, 625000}, + {31, 250000}, + {62, 500000}, + {125, 0}, + {250, 0}, +}; + /** * struct vcnl3020_data - vcnl3020 specific data. * @regmap: device register map. @@ -165,10 +176,51 @@ err_unlock: return rc; } +static int vcnl3020_read_proxy_samp_freq(struct vcnl3020_data *data, int *val, + int *val2) +{ + int rc; + unsigned int prox_rate; + + rc = regmap_read(data->regmap, VCNL_PROXIMITY_RATE, &prox_rate); + if (rc) + return rc; + + if (prox_rate >= ARRAY_SIZE(vcnl3020_prox_sampling_frequency)) + return -EINVAL; + + *val = vcnl3020_prox_sampling_frequency[prox_rate][0]; + *val2 = vcnl3020_prox_sampling_frequency[prox_rate][1]; + + return 0; +} + +static int vcnl3020_write_proxy_samp_freq(struct vcnl3020_data *data, int val, + int val2) +{ + unsigned int i; + int index = -1; + + for (i = 0; i < ARRAY_SIZE(vcnl3020_prox_sampling_frequency); i++) { + if (val == vcnl3020_prox_sampling_frequency[i][0] && + val2 == vcnl3020_prox_sampling_frequency[i][1]) { + index = i; + break; + } + } + + if (index < 0) + return -EINVAL; + + return regmap_write(data->regmap, VCNL_PROXIMITY_RATE, index); +} + static const struct iio_chan_spec vcnl3020_channels[] = { { .type = IIO_PROXIMITY, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ), }, }; @@ -185,6 +237,47 @@ static int vcnl3020_read_raw(struct iio_dev *indio_dev, if (rc) return rc; return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + rc = vcnl3020_read_proxy_samp_freq(data, val, val2); + if (rc < 0) + return rc; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int vcnl3020_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int rc; + struct vcnl3020_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rc = iio_device_claim_direct_mode(indio_dev); + if (rc) + return rc; + rc = vcnl3020_write_proxy_samp_freq(data, val, val2); + iio_device_release_direct_mode(indio_dev); + return rc; + default: + return -EINVAL; + } +} + +static int vcnl3020_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_SAMP_FREQ: + *vals = (int *)vcnl3020_prox_sampling_frequency; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = 2 * ARRAY_SIZE(vcnl3020_prox_sampling_frequency); + return IIO_AVAIL_LIST; default: return -EINVAL; } @@ -192,6 +285,8 @@ static int vcnl3020_read_raw(struct iio_dev *indio_dev, static const struct iio_info vcnl3020_info = { .read_raw = vcnl3020_read_raw, + .write_raw = vcnl3020_write_raw, + .read_avail = vcnl3020_read_avail, }; static const struct regmap_config vcnl3020_regmap_config = { |