Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Thermal device driver for DA9062 and DA9061
0004  * Copyright (C) 2017  Dialog Semiconductor
0005  */
0006 
0007 /* When over-temperature is reached, an interrupt from the device will be
0008  * triggered. Following this event the interrupt will be disabled and
0009  * periodic transmission of uevents (HOT trip point) should define the
0010  * first level of temperature supervision. It is expected that any final
0011  * implementation of the thermal driver will include a .notify() function
0012  * to implement these uevents to userspace.
0013  *
0014  * These uevents are intended to indicate non-invasive temperature control
0015  * of the system, where the necessary measures for cooling are the
0016  * responsibility of the host software. Once the temperature falls again,
0017  * the IRQ is re-enabled so the start of a new over-temperature event can
0018  * be detected without constant software monitoring.
0019  */
0020 
0021 #include <linux/errno.h>
0022 #include <linux/interrupt.h>
0023 #include <linux/module.h>
0024 #include <linux/of.h>
0025 #include <linux/platform_device.h>
0026 #include <linux/regmap.h>
0027 #include <linux/thermal.h>
0028 #include <linux/workqueue.h>
0029 
0030 #include <linux/mfd/da9062/core.h>
0031 #include <linux/mfd/da9062/registers.h>
0032 
0033 /* Minimum, maximum and default polling millisecond periods are provided
0034  * here as an example. It is expected that any final implementation to also
0035  * include a modification of these settings to match the required
0036  * application.
0037  */
0038 #define DA9062_DEFAULT_POLLING_MS_PERIOD    3000
0039 #define DA9062_MAX_POLLING_MS_PERIOD        10000
0040 #define DA9062_MIN_POLLING_MS_PERIOD        1000
0041 
0042 #define DA9062_MILLI_CELSIUS(t)         ((t) * 1000)
0043 
0044 struct da9062_thermal_config {
0045     const char *name;
0046 };
0047 
0048 struct da9062_thermal {
0049     struct da9062 *hw;
0050     struct delayed_work work;
0051     struct thermal_zone_device *zone;
0052     struct mutex lock; /* protection for da9062_thermal temperature */
0053     int temperature;
0054     int irq;
0055     const struct da9062_thermal_config *config;
0056     struct device *dev;
0057 };
0058 
0059 static void da9062_thermal_poll_on(struct work_struct *work)
0060 {
0061     struct da9062_thermal *thermal = container_of(work,
0062                         struct da9062_thermal,
0063                         work.work);
0064     unsigned long delay;
0065     unsigned int val;
0066     int ret;
0067 
0068     /* clear E_TEMP */
0069     ret = regmap_write(thermal->hw->regmap,
0070                DA9062AA_EVENT_B,
0071                DA9062AA_E_TEMP_MASK);
0072     if (ret < 0) {
0073         dev_err(thermal->dev,
0074             "Cannot clear the TJUNC temperature status\n");
0075         goto err_enable_irq;
0076     }
0077 
0078     /* Now read E_TEMP again: it is acting like a status bit.
0079      * If over-temperature, then this status will be true.
0080      * If not over-temperature, this status will be false.
0081      */
0082     ret = regmap_read(thermal->hw->regmap,
0083               DA9062AA_EVENT_B,
0084               &val);
0085     if (ret < 0) {
0086         dev_err(thermal->dev,
0087             "Cannot check the TJUNC temperature status\n");
0088         goto err_enable_irq;
0089     }
0090 
0091     if (val & DA9062AA_E_TEMP_MASK) {
0092         mutex_lock(&thermal->lock);
0093         thermal->temperature = DA9062_MILLI_CELSIUS(125);
0094         mutex_unlock(&thermal->lock);
0095         thermal_zone_device_update(thermal->zone,
0096                        THERMAL_EVENT_UNSPECIFIED);
0097 
0098         delay = thermal->zone->passive_delay_jiffies;
0099         queue_delayed_work(system_freezable_wq, &thermal->work, delay);
0100         return;
0101     }
0102 
0103     mutex_lock(&thermal->lock);
0104     thermal->temperature = DA9062_MILLI_CELSIUS(0);
0105     mutex_unlock(&thermal->lock);
0106     thermal_zone_device_update(thermal->zone,
0107                    THERMAL_EVENT_UNSPECIFIED);
0108 
0109 err_enable_irq:
0110     enable_irq(thermal->irq);
0111 }
0112 
0113 static irqreturn_t da9062_thermal_irq_handler(int irq, void *data)
0114 {
0115     struct da9062_thermal *thermal = data;
0116 
0117     disable_irq_nosync(thermal->irq);
0118     queue_delayed_work(system_freezable_wq, &thermal->work, 0);
0119 
0120     return IRQ_HANDLED;
0121 }
0122 
0123 static int da9062_thermal_get_trip_type(struct thermal_zone_device *z,
0124                     int trip,
0125                     enum thermal_trip_type *type)
0126 {
0127     struct da9062_thermal *thermal = z->devdata;
0128 
0129     switch (trip) {
0130     case 0:
0131         *type = THERMAL_TRIP_HOT;
0132         break;
0133     default:
0134         dev_err(thermal->dev,
0135             "Driver does not support more than 1 trip-wire\n");
0136         return -EINVAL;
0137     }
0138 
0139     return 0;
0140 }
0141 
0142 static int da9062_thermal_get_trip_temp(struct thermal_zone_device *z,
0143                     int trip,
0144                     int *temp)
0145 {
0146     struct da9062_thermal *thermal = z->devdata;
0147 
0148     switch (trip) {
0149     case 0:
0150         *temp = DA9062_MILLI_CELSIUS(125);
0151         break;
0152     default:
0153         dev_err(thermal->dev,
0154             "Driver does not support more than 1 trip-wire\n");
0155         return -EINVAL;
0156     }
0157 
0158     return 0;
0159 }
0160 
0161 static int da9062_thermal_get_temp(struct thermal_zone_device *z,
0162                    int *temp)
0163 {
0164     struct da9062_thermal *thermal = z->devdata;
0165 
0166     mutex_lock(&thermal->lock);
0167     *temp = thermal->temperature;
0168     mutex_unlock(&thermal->lock);
0169 
0170     return 0;
0171 }
0172 
0173 static struct thermal_zone_device_ops da9062_thermal_ops = {
0174     .get_temp   = da9062_thermal_get_temp,
0175     .get_trip_type  = da9062_thermal_get_trip_type,
0176     .get_trip_temp  = da9062_thermal_get_trip_temp,
0177 };
0178 
0179 static const struct da9062_thermal_config da9062_config = {
0180     .name = "da9062-thermal",
0181 };
0182 
0183 static const struct of_device_id da9062_compatible_reg_id_table[] = {
0184     { .compatible = "dlg,da9062-thermal", .data = &da9062_config },
0185     { },
0186 };
0187 
0188 MODULE_DEVICE_TABLE(of, da9062_compatible_reg_id_table);
0189 
0190 static int da9062_thermal_probe(struct platform_device *pdev)
0191 {
0192     struct da9062 *chip = dev_get_drvdata(pdev->dev.parent);
0193     struct da9062_thermal *thermal;
0194     unsigned int pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD;
0195     const struct of_device_id *match;
0196     int ret = 0;
0197 
0198     match = of_match_node(da9062_compatible_reg_id_table,
0199                   pdev->dev.of_node);
0200     if (!match)
0201         return -ENXIO;
0202 
0203     if (pdev->dev.of_node) {
0204         if (!of_property_read_u32(pdev->dev.of_node,
0205                       "polling-delay-passive",
0206                       &pp_tmp)) {
0207             if (pp_tmp < DA9062_MIN_POLLING_MS_PERIOD ||
0208                 pp_tmp > DA9062_MAX_POLLING_MS_PERIOD) {
0209                 dev_warn(&pdev->dev,
0210                      "Out-of-range polling period %d ms\n",
0211                      pp_tmp);
0212                 pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD;
0213             }
0214         }
0215     }
0216 
0217     thermal = devm_kzalloc(&pdev->dev, sizeof(struct da9062_thermal),
0218                    GFP_KERNEL);
0219     if (!thermal) {
0220         ret = -ENOMEM;
0221         goto err;
0222     }
0223 
0224     thermal->config = match->data;
0225     thermal->hw = chip;
0226     thermal->dev = &pdev->dev;
0227 
0228     INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on);
0229     mutex_init(&thermal->lock);
0230 
0231     thermal->zone = thermal_zone_device_register(thermal->config->name,
0232                     1, 0, thermal,
0233                     &da9062_thermal_ops, NULL, pp_tmp,
0234                     0);
0235     if (IS_ERR(thermal->zone)) {
0236         dev_err(&pdev->dev, "Cannot register thermal zone device\n");
0237         ret = PTR_ERR(thermal->zone);
0238         goto err;
0239     }
0240     ret = thermal_zone_device_enable(thermal->zone);
0241     if (ret) {
0242         dev_err(&pdev->dev, "Cannot enable thermal zone device\n");
0243         goto err_zone;
0244     }
0245 
0246     dev_dbg(&pdev->dev,
0247         "TJUNC temperature polling period set at %d ms\n",
0248         jiffies_to_msecs(thermal->zone->passive_delay_jiffies));
0249 
0250     ret = platform_get_irq_byname(pdev, "THERMAL");
0251     if (ret < 0) {
0252         dev_err(&pdev->dev, "Failed to get platform IRQ.\n");
0253         goto err_zone;
0254     }
0255     thermal->irq = ret;
0256 
0257     ret = request_threaded_irq(thermal->irq, NULL,
0258                    da9062_thermal_irq_handler,
0259                    IRQF_TRIGGER_LOW | IRQF_ONESHOT,
0260                    "THERMAL", thermal);
0261     if (ret) {
0262         dev_err(&pdev->dev,
0263             "Failed to request thermal device IRQ.\n");
0264         goto err_zone;
0265     }
0266 
0267     platform_set_drvdata(pdev, thermal);
0268     return 0;
0269 
0270 err_zone:
0271     thermal_zone_device_unregister(thermal->zone);
0272 err:
0273     return ret;
0274 }
0275 
0276 static int da9062_thermal_remove(struct platform_device *pdev)
0277 {
0278     struct  da9062_thermal *thermal = platform_get_drvdata(pdev);
0279 
0280     free_irq(thermal->irq, thermal);
0281     cancel_delayed_work_sync(&thermal->work);
0282     thermal_zone_device_unregister(thermal->zone);
0283     return 0;
0284 }
0285 
0286 static struct platform_driver da9062_thermal_driver = {
0287     .probe  = da9062_thermal_probe,
0288     .remove = da9062_thermal_remove,
0289     .driver = {
0290         .name   = "da9062-thermal",
0291         .of_match_table = da9062_compatible_reg_id_table,
0292     },
0293 };
0294 
0295 module_platform_driver(da9062_thermal_driver);
0296 
0297 MODULE_AUTHOR("Steve Twiss");
0298 MODULE_DESCRIPTION("Thermal TJUNC device driver for Dialog DA9062 and DA9061");
0299 MODULE_LICENSE("GPL");
0300 MODULE_ALIAS("platform:da9062-thermal");