| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright 2021 Google LLC. |
| * |
| * Common part of most Semtech SAR sensor. |
| */ |
| |
| #include <linux/bitops.h> |
| #include <linux/byteorder/generic.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/export.h> |
| #include <linux/interrupt.h> |
| #include <linux/irqreturn.h> |
| #include <linux/i2c.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| #include <vdso/bits.h> |
| |
| #include <linux/iio/buffer.h> |
| #include <linux/iio/events.h> |
| #include <linux/iio/iio.h> |
| #include <linux/iio/trigger.h> |
| #include <linux/iio/triggered_buffer.h> |
| #include <linux/iio/trigger_consumer.h> |
| |
| #include "sx_common.h" |
| |
| /* All Semtech SAR sensors have IRQ bit in the same order. */ |
| #define SX_COMMON_CONVDONE_IRQ BIT(0) |
| #define SX_COMMON_FAR_IRQ BIT(2) |
| #define SX_COMMON_CLOSE_IRQ BIT(3) |
| |
| const struct iio_event_spec sx_common_events[3] = { |
| { |
| .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) | |
| BIT(IIO_EV_INFO_HYSTERESIS) | |
| BIT(IIO_EV_INFO_VALUE), |
| }, |
| }; |
| EXPORT_SYMBOL_NS_GPL(sx_common_events, SEMTECH_PROX); |
| |
| static irqreturn_t sx_common_irq_handler(int irq, void *private) |
| { |
| struct iio_dev *indio_dev = private; |
| struct sx_common_data *data = iio_priv(indio_dev); |
| |
| if (data->trigger_enabled) |
| iio_trigger_poll(data->trig); |
| |
| /* |
| * Even if no event is enabled, we need to wake the thread to clear the |
| * interrupt state by reading SX_COMMON_REG_IRQ_SRC. |
| * It is not possible to do that here because regmap_read takes a mutex. |
| */ |
| return IRQ_WAKE_THREAD; |
| } |
| |
| static void sx_common_push_events(struct iio_dev *indio_dev) |
| { |
| int ret; |
| unsigned int val, chan; |
| struct sx_common_data *data = iio_priv(indio_dev); |
| s64 timestamp = iio_get_time_ns(indio_dev); |
| unsigned long prox_changed; |
| |
| /* Read proximity state on all channels */ |
| ret = regmap_read(data->regmap, data->chip_info->reg_stat, &val); |
| if (ret) { |
| dev_err(&data->client->dev, "i2c transfer error in irq\n"); |
| return; |
| } |
| |
| val >>= data->chip_info->stat_offset; |
| |
| /* |
| * Only iterate over channels with changes on proximity status that have |
| * events enabled. |
| */ |
| prox_changed = (data->chan_prox_stat ^ val) & data->chan_event; |
| |
| for_each_set_bit(chan, &prox_changed, data->chip_info->num_channels) { |
| int dir; |
| u64 ev; |
| |
| dir = (val & BIT(chan)) ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; |
| ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan, |
| IIO_EV_TYPE_THRESH, dir); |
| |
| iio_push_event(indio_dev, ev, timestamp); |
| } |
| data->chan_prox_stat = val; |
| } |
| |
| static int sx_common_enable_irq(struct sx_common_data *data, unsigned int irq) |
| { |
| if (!data->client->irq) |
| return 0; |
| return regmap_update_bits(data->regmap, data->chip_info->reg_irq_msk, |
| irq << data->chip_info->irq_msk_offset, |
| irq << data->chip_info->irq_msk_offset); |
| } |
| |
| static int sx_common_disable_irq(struct sx_common_data *data, unsigned int irq) |
| { |
| if (!data->client->irq) |
| return 0; |
| return regmap_update_bits(data->regmap, data->chip_info->reg_irq_msk, |
| irq << data->chip_info->irq_msk_offset, 0); |
| } |
| |
| static int sx_common_update_chan_en(struct sx_common_data *data, |
| unsigned long chan_read, |
| unsigned long chan_event) |
| { |
| int ret; |
| unsigned long channels = chan_read | chan_event; |
| |
| if ((data->chan_read | data->chan_event) != channels) { |
| ret = regmap_update_bits(data->regmap, |
| data->chip_info->reg_enable_chan, |
| data->chip_info->mask_enable_chan, |
| channels); |
| if (ret) |
| return ret; |
| } |
| data->chan_read = chan_read; |
| data->chan_event = chan_event; |
| return 0; |
| } |
| |
| static int sx_common_get_read_channel(struct sx_common_data *data, int channel) |
| { |
| return sx_common_update_chan_en(data, data->chan_read | BIT(channel), |
| data->chan_event); |
| } |
| |
| static int sx_common_put_read_channel(struct sx_common_data *data, int channel) |
| { |
| return sx_common_update_chan_en(data, data->chan_read & ~BIT(channel), |
| data->chan_event); |
| } |
| |
| static int sx_common_get_event_channel(struct sx_common_data *data, int channel) |
| { |
| return sx_common_update_chan_en(data, data->chan_read, |
| data->chan_event | BIT(channel)); |
| } |
| |
| static int sx_common_put_event_channel(struct sx_common_data *data, int channel) |
| { |
| return sx_common_update_chan_en(data, data->chan_read, |
| data->chan_event & ~BIT(channel)); |
| } |
| |
| /** |
| * sx_common_read_proximity() - Read raw proximity value. |
| * @data: Internal data |
| * @chan: Channel to read |
| * @val: pointer to return read value. |
| * |
| * Request a conversion, wait for the sensor to be ready and |
| * return the raw proximity value. |
| */ |
| int sx_common_read_proximity(struct sx_common_data *data, |
| const struct iio_chan_spec *chan, int *val) |
| { |
| int ret; |
| __be16 rawval; |
| |
| mutex_lock(&data->mutex); |
| |
| ret = sx_common_get_read_channel(data, chan->channel); |
| if (ret) |
| goto out; |
| |
| ret = sx_common_enable_irq(data, SX_COMMON_CONVDONE_IRQ); |
| if (ret) |
| goto out_put_channel; |
| |
| mutex_unlock(&data->mutex); |
| |
| if (data->client->irq) { |
| ret = wait_for_completion_interruptible(&data->completion); |
| reinit_completion(&data->completion); |
| } else { |
| ret = data->chip_info->ops.wait_for_sample(data); |
| } |
| |
| mutex_lock(&data->mutex); |
| |
| if (ret) |
| goto out_disable_irq; |
| |
| ret = data->chip_info->ops.read_prox_data(data, chan, &rawval); |
| if (ret) |
| goto out_disable_irq; |
| |
| *val = sign_extend32(be16_to_cpu(rawval), chan->scan_type.realbits - 1); |
| |
| ret = sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ); |
| if (ret) |
| goto out_put_channel; |
| |
| ret = sx_common_put_read_channel(data, chan->channel); |
| if (ret) |
| goto out; |
| |
| mutex_unlock(&data->mutex); |
| |
| return IIO_VAL_INT; |
| |
| out_disable_irq: |
| sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ); |
| out_put_channel: |
| sx_common_put_read_channel(data, chan->channel); |
| out: |
| mutex_unlock(&data->mutex); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_NS_GPL(sx_common_read_proximity, SEMTECH_PROX); |
| |
| /** |
| * sx_common_read_event_config() - Configure event setting. |
| * @indio_dev: iio device object |
| * @chan: Channel to read |
| * @type: Type of event (unused) |
| * @dir: Direction of event (unused) |
| * |
| * return if the given channel is used for event gathering. |
| */ |
| int sx_common_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 sx_common_data *data = iio_priv(indio_dev); |
| |
| return !!(data->chan_event & BIT(chan->channel)); |
| } |
| EXPORT_SYMBOL_NS_GPL(sx_common_read_event_config, SEMTECH_PROX); |
| |
| /** |
| * sx_common_write_event_config() - Configure event setting. |
| * @indio_dev: iio device object |
| * @chan: Channel to enable |
| * @type: Type of event (unused) |
| * @dir: Direction of event (unused) |
| * @state: State of the event. |
| * |
| * Enable/Disable event on a given channel. |
| */ |
| int sx_common_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 sx_common_data *data = iio_priv(indio_dev); |
| unsigned int eventirq = SX_COMMON_FAR_IRQ | SX_COMMON_CLOSE_IRQ; |
| int ret; |
| |
| /* If the state hasn't changed, there's nothing to do. */ |
| if (!!(data->chan_event & BIT(chan->channel)) == state) |
| return 0; |
| |
| mutex_lock(&data->mutex); |
| if (state) { |
| ret = sx_common_get_event_channel(data, chan->channel); |
| if (ret) |
| goto out_unlock; |
| if (!(data->chan_event & ~BIT(chan->channel))) { |
| ret = sx_common_enable_irq(data, eventirq); |
| if (ret) |
| sx_common_put_event_channel(data, chan->channel); |
| } |
| } else { |
| ret = sx_common_put_event_channel(data, chan->channel); |
| if (ret) |
| goto out_unlock; |
| if (!data->chan_event) { |
| ret = sx_common_disable_irq(data, eventirq); |
| if (ret) |
| sx_common_get_event_channel(data, chan->channel); |
| } |
| } |
| |
| out_unlock: |
| mutex_unlock(&data->mutex); |
| return ret; |
| } |
| EXPORT_SYMBOL_NS_GPL(sx_common_write_event_config, SEMTECH_PROX); |
| |
| static int sx_common_set_trigger_state(struct iio_trigger *trig, bool state) |
| { |
| struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); |
| struct sx_common_data *data = iio_priv(indio_dev); |
| int ret = 0; |
| |
| mutex_lock(&data->mutex); |
| |
| if (state) |
| ret = sx_common_enable_irq(data, SX_COMMON_CONVDONE_IRQ); |
| else if (!data->chan_read) |
| ret = sx_common_disable_irq(data, SX_COMMON_CONVDONE_IRQ); |
| if (ret) |
| goto out; |
| |
| data->trigger_enabled = state; |
| |
| out: |
| mutex_unlock(&data->mutex); |
| |
| return ret; |
| } |
| |
| static const struct iio_trigger_ops sx_common_trigger_ops = { |
| .set_trigger_state = sx_common_set_trigger_state, |
| }; |
| |
| static irqreturn_t sx_common_irq_thread_handler(int irq, void *private) |
| { |
| struct iio_dev *indio_dev = private; |
| struct sx_common_data *data = iio_priv(indio_dev); |
| int ret; |
| unsigned int val; |
| |
| mutex_lock(&data->mutex); |
| |
| ret = regmap_read(data->regmap, SX_COMMON_REG_IRQ_SRC, &val); |
| if (ret) { |
| dev_err(&data->client->dev, "i2c transfer error in irq\n"); |
| goto out; |
| } |
| |
| if (val & ((SX_COMMON_FAR_IRQ | SX_COMMON_CLOSE_IRQ) << data->chip_info->irq_msk_offset)) |
| sx_common_push_events(indio_dev); |
| |
| if (val & (SX_COMMON_CONVDONE_IRQ << data->chip_info->irq_msk_offset)) |
| complete(&data->completion); |
| |
| out: |
| mutex_unlock(&data->mutex); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t sx_common_trigger_handler(int irq, void *private) |
| { |
| struct iio_poll_func *pf = private; |
| struct iio_dev *indio_dev = pf->indio_dev; |
| struct sx_common_data *data = iio_priv(indio_dev); |
| __be16 val; |
| int bit, ret, i = 0; |
| |
| mutex_lock(&data->mutex); |
| |
| for_each_set_bit(bit, indio_dev->active_scan_mask, |
| indio_dev->masklength) { |
| ret = data->chip_info->ops.read_prox_data(data, |
| &indio_dev->channels[bit], |
| &val); |
| if (ret) |
| goto out; |
| |
| data->buffer.channels[i++] = val; |
| } |
| |
| iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, |
| pf->timestamp); |
| |
| out: |
| mutex_unlock(&data->mutex); |
| |
| iio_trigger_notify_done(indio_dev->trig); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int sx_common_buffer_preenable(struct iio_dev *indio_dev) |
| { |
| struct sx_common_data *data = iio_priv(indio_dev); |
| unsigned long channels = 0; |
| int bit, ret; |
| |
| mutex_lock(&data->mutex); |
| for_each_set_bit(bit, indio_dev->active_scan_mask, |
| indio_dev->masklength) |
| __set_bit(indio_dev->channels[bit].channel, &channels); |
| |
| ret = sx_common_update_chan_en(data, channels, data->chan_event); |
| mutex_unlock(&data->mutex); |
| return ret; |
| } |
| |
| static int sx_common_buffer_postdisable(struct iio_dev *indio_dev) |
| { |
| struct sx_common_data *data = iio_priv(indio_dev); |
| int ret; |
| |
| mutex_lock(&data->mutex); |
| ret = sx_common_update_chan_en(data, 0, data->chan_event); |
| mutex_unlock(&data->mutex); |
| return ret; |
| } |
| |
| static const struct iio_buffer_setup_ops sx_common_buffer_setup_ops = { |
| .preenable = sx_common_buffer_preenable, |
| .postdisable = sx_common_buffer_postdisable, |
| }; |
| |
| static void sx_common_regulator_disable(void *_data) |
| { |
| struct sx_common_data *data = _data; |
| |
| regulator_bulk_disable(ARRAY_SIZE(data->supplies), data->supplies); |
| } |
| |
| #define SX_COMMON_SOFT_RESET 0xde |
| |
| static int sx_common_init_device(struct device *dev, struct iio_dev *indio_dev) |
| { |
| struct sx_common_data *data = iio_priv(indio_dev); |
| struct sx_common_reg_default tmp; |
| const struct sx_common_reg_default *initval; |
| int ret; |
| unsigned int i, val; |
| |
| ret = regmap_write(data->regmap, data->chip_info->reg_reset, |
| SX_COMMON_SOFT_RESET); |
| if (ret) |
| return ret; |
| |
| usleep_range(1000, 2000); /* power-up time is ~1ms. */ |
| |
| /* Clear reset interrupt state by reading SX_COMMON_REG_IRQ_SRC. */ |
| ret = regmap_read(data->regmap, SX_COMMON_REG_IRQ_SRC, &val); |
| if (ret) |
| return ret; |
| |
| /* Program defaults from constant or BIOS. */ |
| for (i = 0; i < data->chip_info->num_default_regs; i++) { |
| initval = data->chip_info->ops.get_default_reg(dev, i, &tmp); |
| ret = regmap_write(data->regmap, initval->reg, initval->def); |
| if (ret) |
| return ret; |
| } |
| |
| return data->chip_info->ops.init_compensation(indio_dev); |
| } |
| |
| /** |
| * sx_common_probe() - Common setup for Semtech SAR sensor |
| * @client: I2C client object |
| * @chip_info: Semtech sensor chip information. |
| * @regmap_config: Sensor registers map configuration. |
| */ |
| int sx_common_probe(struct i2c_client *client, |
| const struct sx_common_chip_info *chip_info, |
| const struct regmap_config *regmap_config) |
| { |
| struct device *dev = &client->dev; |
| struct iio_dev *indio_dev; |
| struct sx_common_data *data; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); |
| if (!indio_dev) |
| return -ENOMEM; |
| |
| data = iio_priv(indio_dev); |
| |
| data->chip_info = chip_info; |
| data->client = client; |
| data->supplies[0].supply = "vdd"; |
| data->supplies[1].supply = "svdd"; |
| mutex_init(&data->mutex); |
| init_completion(&data->completion); |
| |
| data->regmap = devm_regmap_init_i2c(client, regmap_config); |
| if (IS_ERR(data->regmap)) |
| return dev_err_probe(dev, PTR_ERR(data->regmap), |
| "Could init register map\n"); |
| |
| ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), |
| data->supplies); |
| if (ret) |
| return dev_err_probe(dev, ret, "Unable to get regulators\n"); |
| |
| ret = regulator_bulk_enable(ARRAY_SIZE(data->supplies), data->supplies); |
| if (ret) |
| return dev_err_probe(dev, ret, "Unable to enable regulators\n"); |
| |
| /* Must wait for Tpor time after initial power up */ |
| usleep_range(1000, 1100); |
| |
| ret = devm_add_action_or_reset(dev, sx_common_regulator_disable, data); |
| if (ret) |
| return dev_err_probe(dev, ret, |
| "Unable to register regulators deleter\n"); |
| |
| ret = data->chip_info->ops.check_whoami(dev, indio_dev); |
| if (ret) |
| return dev_err_probe(dev, ret, "error reading WHOAMI\n"); |
| |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| |
| indio_dev->channels = data->chip_info->iio_channels; |
| indio_dev->num_channels = data->chip_info->num_iio_channels; |
| indio_dev->info = &data->chip_info->iio_info; |
| |
| i2c_set_clientdata(client, indio_dev); |
| |
| ret = sx_common_init_device(dev, indio_dev); |
| if (ret) |
| return dev_err_probe(dev, ret, "Unable to initialize sensor\n"); |
| |
| if (client->irq) { |
| ret = devm_request_threaded_irq(dev, client->irq, |
| sx_common_irq_handler, |
| sx_common_irq_thread_handler, |
| IRQF_ONESHOT, |
| "sx_event", indio_dev); |
| if (ret) |
| return dev_err_probe(dev, ret, "No IRQ\n"); |
| |
| 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 = &sx_common_trigger_ops; |
| iio_trigger_set_drvdata(data->trig, indio_dev); |
| |
| ret = devm_iio_trigger_register(dev, data->trig); |
| if (ret) |
| return ret; |
| } |
| |
| ret = devm_iio_triggered_buffer_setup(dev, indio_dev, |
| iio_pollfunc_store_time, |
| sx_common_trigger_handler, |
| &sx_common_buffer_setup_ops); |
| if (ret) |
| return ret; |
| |
| return devm_iio_device_register(dev, indio_dev); |
| } |
| EXPORT_SYMBOL_NS_GPL(sx_common_probe, SEMTECH_PROX); |
| |
| MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>"); |
| MODULE_DESCRIPTION("Common functions and structures for Semtech sensor"); |
| MODULE_LICENSE("GPL v2"); |