| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * STMicroelectronics hts221 sensor driver | |
| * | |
| * Copyright 2016 STMicroelectronics Inc. | |
| * | |
| * Lorenzo Bianconi <lorenzo.bianconi@st.com> | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/device.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/irqreturn.h> | |
| #include <linux/regmap.h> | |
| #include <linux/bitfield.h> | |
| #include <linux/iio/iio.h> | |
| #include <linux/iio/trigger.h> | |
| #include <linux/iio/events.h> | |
| #include <linux/iio/trigger_consumer.h> | |
| #include <linux/iio/triggered_buffer.h> | |
| #include <linux/iio/buffer.h> | |
| #include <linux/platform_data/st_sensors_pdata.h> | |
| #include "hts221.h" | |
| #define HTS221_REG_DRDY_HL_ADDR 0x22 | |
| #define HTS221_REG_DRDY_HL_MASK BIT(7) | |
| #define HTS221_REG_DRDY_PP_OD_ADDR 0x22 | |
| #define HTS221_REG_DRDY_PP_OD_MASK BIT(6) | |
| #define HTS221_REG_DRDY_EN_ADDR 0x22 | |
| #define HTS221_REG_DRDY_EN_MASK BIT(2) | |
| #define HTS221_REG_STATUS_ADDR 0x27 | |
| #define HTS221_RH_DRDY_MASK BIT(1) | |
| #define HTS221_TEMP_DRDY_MASK BIT(0) | |
| static int hts221_trig_set_state(struct iio_trigger *trig, bool state) | |
| { | |
| struct iio_dev *iio_dev = iio_trigger_get_drvdata(trig); | |
| struct hts221_hw *hw = iio_priv(iio_dev); | |
| return regmap_update_bits(hw->regmap, HTS221_REG_DRDY_EN_ADDR, | |
| HTS221_REG_DRDY_EN_MASK, | |
| FIELD_PREP(HTS221_REG_DRDY_EN_MASK, state)); | |
| } | |
| static const struct iio_trigger_ops hts221_trigger_ops = { | |
| .set_trigger_state = hts221_trig_set_state, | |
| }; | |
| static irqreturn_t hts221_trigger_handler_thread(int irq, void *private) | |
| { | |
| struct hts221_hw *hw = private; | |
| int err, status; | |
| err = regmap_read(hw->regmap, HTS221_REG_STATUS_ADDR, &status); | |
| if (err < 0) | |
| return IRQ_HANDLED; | |
| /* | |
| * H_DA bit (humidity data available) is routed to DRDY line. | |
| * Humidity sample is computed after temperature one. | |
| * Here we can assume data channels are both available if H_DA bit | |
| * is set in status register | |
| */ | |
| if (!(status & HTS221_RH_DRDY_MASK)) | |
| return IRQ_NONE; | |
| iio_trigger_poll_chained(hw->trig); | |
| return IRQ_HANDLED; | |
| } | |
| int hts221_allocate_trigger(struct iio_dev *iio_dev) | |
| { | |
| struct hts221_hw *hw = iio_priv(iio_dev); | |
| struct st_sensors_platform_data *pdata = dev_get_platdata(hw->dev); | |
| bool irq_active_low = false, open_drain = false; | |
| unsigned long irq_type; | |
| int err; | |
| irq_type = irqd_get_trigger_type(irq_get_irq_data(hw->irq)); | |
| switch (irq_type) { | |
| case IRQF_TRIGGER_HIGH: | |
| case IRQF_TRIGGER_RISING: | |
| break; | |
| case IRQF_TRIGGER_LOW: | |
| case IRQF_TRIGGER_FALLING: | |
| irq_active_low = true; | |
| break; | |
| default: | |
| dev_info(hw->dev, | |
| "mode %lx unsupported, using IRQF_TRIGGER_RISING\n", | |
| irq_type); | |
| irq_type = IRQF_TRIGGER_RISING; | |
| break; | |
| } | |
| err = regmap_update_bits(hw->regmap, HTS221_REG_DRDY_HL_ADDR, | |
| HTS221_REG_DRDY_HL_MASK, | |
| FIELD_PREP(HTS221_REG_DRDY_HL_MASK, | |
| irq_active_low)); | |
| if (err < 0) | |
| return err; | |
| if (device_property_read_bool(hw->dev, "drive-open-drain") || | |
| (pdata && pdata->open_drain)) { | |
| irq_type |= IRQF_SHARED; | |
| open_drain = true; | |
| } | |
| err = regmap_update_bits(hw->regmap, HTS221_REG_DRDY_PP_OD_ADDR, | |
| HTS221_REG_DRDY_PP_OD_MASK, | |
| FIELD_PREP(HTS221_REG_DRDY_PP_OD_MASK, | |
| open_drain)); | |
| if (err < 0) | |
| return err; | |
| err = devm_request_threaded_irq(hw->dev, hw->irq, NULL, | |
| hts221_trigger_handler_thread, | |
| irq_type | IRQF_ONESHOT, | |
| hw->name, hw); | |
| if (err) { | |
| dev_err(hw->dev, "failed to request trigger irq %d\n", | |
| hw->irq); | |
| return err; | |
| } | |
| hw->trig = devm_iio_trigger_alloc(hw->dev, "%s-trigger", | |
| iio_dev->name); | |
| if (!hw->trig) | |
| return -ENOMEM; | |
| iio_trigger_set_drvdata(hw->trig, iio_dev); | |
| hw->trig->ops = &hts221_trigger_ops; | |
| hw->trig->dev.parent = hw->dev; | |
| iio_dev->trig = iio_trigger_get(hw->trig); | |
| return devm_iio_trigger_register(hw->dev, hw->trig); | |
| } | |
| static int hts221_buffer_preenable(struct iio_dev *iio_dev) | |
| { | |
| return hts221_set_enable(iio_priv(iio_dev), true); | |
| } | |
| static int hts221_buffer_postdisable(struct iio_dev *iio_dev) | |
| { | |
| return hts221_set_enable(iio_priv(iio_dev), false); | |
| } | |
| static const struct iio_buffer_setup_ops hts221_buffer_ops = { | |
| .preenable = hts221_buffer_preenable, | |
| .postdisable = hts221_buffer_postdisable, | |
| }; | |
| static irqreturn_t hts221_buffer_handler_thread(int irq, void *p) | |
| { | |
| struct iio_poll_func *pf = p; | |
| struct iio_dev *iio_dev = pf->indio_dev; | |
| struct hts221_hw *hw = iio_priv(iio_dev); | |
| struct iio_chan_spec const *ch; | |
| int err; | |
| /* humidity data */ | |
| ch = &iio_dev->channels[HTS221_SENSOR_H]; | |
| err = regmap_bulk_read(hw->regmap, ch->address, | |
| &hw->scan.channels[0], | |
| sizeof(hw->scan.channels[0])); | |
| if (err < 0) | |
| goto out; | |
| /* temperature data */ | |
| ch = &iio_dev->channels[HTS221_SENSOR_T]; | |
| err = regmap_bulk_read(hw->regmap, ch->address, | |
| &hw->scan.channels[1], | |
| sizeof(hw->scan.channels[1])); | |
| if (err < 0) | |
| goto out; | |
| iio_push_to_buffers_with_timestamp(iio_dev, &hw->scan, | |
| iio_get_time_ns(iio_dev)); | |
| out: | |
| iio_trigger_notify_done(hw->trig); | |
| return IRQ_HANDLED; | |
| } | |
| int hts221_allocate_buffers(struct iio_dev *iio_dev) | |
| { | |
| struct hts221_hw *hw = iio_priv(iio_dev); | |
| return devm_iio_triggered_buffer_setup(hw->dev, iio_dev, | |
| NULL, hts221_buffer_handler_thread, | |
| &hts221_buffer_ops); | |
| } | |
| MODULE_AUTHOR("Lorenzo Bianconi <lorenzo.bianconi@st.com>"); | |
| MODULE_DESCRIPTION("STMicroelectronics hts221 buffer driver"); | |
| MODULE_LICENSE("GPL v2"); |