| // 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"); |