linux/drivers/thermal/tegra/tegra-bpmp-thermal.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2015-2017, NVIDIA CORPORATION.  All rights reserved.
   4 *
   5 * Author:
   6 *      Mikko Perttunen <mperttunen@nvidia.com>
   7 *      Aapo Vienamo    <avienamo@nvidia.com>
   8 */
   9
  10#include <linux/err.h>
  11#include <linux/module.h>
  12#include <linux/platform_device.h>
  13#include <linux/thermal.h>
  14#include <linux/workqueue.h>
  15
  16#include <soc/tegra/bpmp.h>
  17#include <soc/tegra/bpmp-abi.h>
  18
  19struct tegra_bpmp_thermal_zone {
  20        struct tegra_bpmp_thermal *tegra;
  21        struct thermal_zone_device *tzd;
  22        struct work_struct tz_device_update_work;
  23        unsigned int idx;
  24};
  25
  26struct tegra_bpmp_thermal {
  27        struct device *dev;
  28        struct tegra_bpmp *bpmp;
  29        unsigned int num_zones;
  30        struct tegra_bpmp_thermal_zone **zones;
  31};
  32
  33static int tegra_bpmp_thermal_get_temp(void *data, int *out_temp)
  34{
  35        struct tegra_bpmp_thermal_zone *zone = data;
  36        struct mrq_thermal_host_to_bpmp_request req;
  37        union mrq_thermal_bpmp_to_host_response reply;
  38        struct tegra_bpmp_message msg;
  39        int err;
  40
  41        memset(&req, 0, sizeof(req));
  42        req.type = CMD_THERMAL_GET_TEMP;
  43        req.get_temp.zone = zone->idx;
  44
  45        memset(&msg, 0, sizeof(msg));
  46        msg.mrq = MRQ_THERMAL;
  47        msg.tx.data = &req;
  48        msg.tx.size = sizeof(req);
  49        msg.rx.data = &reply;
  50        msg.rx.size = sizeof(reply);
  51
  52        err = tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
  53        if (err)
  54                return err;
  55
  56        *out_temp = reply.get_temp.temp;
  57
  58        return 0;
  59}
  60
  61static int tegra_bpmp_thermal_set_trips(void *data, int low, int high)
  62{
  63        struct tegra_bpmp_thermal_zone *zone = data;
  64        struct mrq_thermal_host_to_bpmp_request req;
  65        struct tegra_bpmp_message msg;
  66
  67        memset(&req, 0, sizeof(req));
  68        req.type = CMD_THERMAL_SET_TRIP;
  69        req.set_trip.zone = zone->idx;
  70        req.set_trip.enabled = true;
  71        req.set_trip.low = low;
  72        req.set_trip.high = high;
  73
  74        memset(&msg, 0, sizeof(msg));
  75        msg.mrq = MRQ_THERMAL;
  76        msg.tx.data = &req;
  77        msg.tx.size = sizeof(req);
  78
  79        return tegra_bpmp_transfer(zone->tegra->bpmp, &msg);
  80}
  81
  82static void tz_device_update_work_fn(struct work_struct *work)
  83{
  84        struct tegra_bpmp_thermal_zone *zone;
  85
  86        zone = container_of(work, struct tegra_bpmp_thermal_zone,
  87                            tz_device_update_work);
  88
  89        thermal_zone_device_update(zone->tzd, THERMAL_TRIP_VIOLATED);
  90}
  91
  92static void bpmp_mrq_thermal(unsigned int mrq, struct tegra_bpmp_channel *ch,
  93                             void *data)
  94{
  95        struct mrq_thermal_bpmp_to_host_request *req;
  96        struct tegra_bpmp_thermal *tegra = data;
  97        int i;
  98
  99        req = (struct mrq_thermal_bpmp_to_host_request *)ch->ib->data;
 100
 101        if (req->type != CMD_THERMAL_HOST_TRIP_REACHED) {
 102                dev_err(tegra->dev, "%s: invalid request type: %d\n",
 103                        __func__, req->type);
 104                tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0);
 105                return;
 106        }
 107
 108        for (i = 0; i < tegra->num_zones; ++i) {
 109                if (tegra->zones[i]->idx != req->host_trip_reached.zone)
 110                        continue;
 111
 112                schedule_work(&tegra->zones[i]->tz_device_update_work);
 113                tegra_bpmp_mrq_return(ch, 0, NULL, 0);
 114                return;
 115        }
 116
 117        dev_err(tegra->dev, "%s: invalid thermal zone: %d\n", __func__,
 118                req->host_trip_reached.zone);
 119        tegra_bpmp_mrq_return(ch, -EINVAL, NULL, 0);
 120}
 121
 122static int tegra_bpmp_thermal_get_num_zones(struct tegra_bpmp *bpmp,
 123                                            int *num_zones)
 124{
 125        struct mrq_thermal_host_to_bpmp_request req;
 126        union mrq_thermal_bpmp_to_host_response reply;
 127        struct tegra_bpmp_message msg;
 128        int err;
 129
 130        memset(&req, 0, sizeof(req));
 131        req.type = CMD_THERMAL_GET_NUM_ZONES;
 132
 133        memset(&msg, 0, sizeof(msg));
 134        msg.mrq = MRQ_THERMAL;
 135        msg.tx.data = &req;
 136        msg.tx.size = sizeof(req);
 137        msg.rx.data = &reply;
 138        msg.rx.size = sizeof(reply);
 139
 140        err = tegra_bpmp_transfer(bpmp, &msg);
 141        if (err)
 142                return err;
 143
 144        *num_zones = reply.get_num_zones.num;
 145
 146        return 0;
 147}
 148
 149static const struct thermal_zone_of_device_ops tegra_bpmp_of_thermal_ops = {
 150        .get_temp = tegra_bpmp_thermal_get_temp,
 151        .set_trips = tegra_bpmp_thermal_set_trips,
 152};
 153
 154static int tegra_bpmp_thermal_probe(struct platform_device *pdev)
 155{
 156        struct tegra_bpmp *bpmp = dev_get_drvdata(pdev->dev.parent);
 157        struct tegra_bpmp_thermal *tegra;
 158        struct thermal_zone_device *tzd;
 159        unsigned int i, max_num_zones;
 160        int err;
 161
 162        tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
 163        if (!tegra)
 164                return -ENOMEM;
 165
 166        tegra->dev = &pdev->dev;
 167        tegra->bpmp = bpmp;
 168
 169        err = tegra_bpmp_thermal_get_num_zones(bpmp, &max_num_zones);
 170        if (err) {
 171                dev_err(&pdev->dev, "failed to get the number of zones: %d\n",
 172                        err);
 173                return err;
 174        }
 175
 176        tegra->zones = devm_kcalloc(&pdev->dev, max_num_zones,
 177                                    sizeof(*tegra->zones), GFP_KERNEL);
 178        if (!tegra->zones)
 179                return -ENOMEM;
 180
 181        for (i = 0; i < max_num_zones; ++i) {
 182                struct tegra_bpmp_thermal_zone *zone;
 183                int temp;
 184
 185                zone = devm_kzalloc(&pdev->dev, sizeof(*zone), GFP_KERNEL);
 186                if (!zone)
 187                        return -ENOMEM;
 188
 189                zone->idx = i;
 190                zone->tegra = tegra;
 191
 192                err = tegra_bpmp_thermal_get_temp(zone, &temp);
 193                if (err < 0) {
 194                        devm_kfree(&pdev->dev, zone);
 195                        continue;
 196                }
 197
 198                tzd = devm_thermal_zone_of_sensor_register(
 199                        &pdev->dev, i, zone, &tegra_bpmp_of_thermal_ops);
 200                if (IS_ERR(tzd)) {
 201                        if (PTR_ERR(tzd) == -EPROBE_DEFER)
 202                                return -EPROBE_DEFER;
 203                        devm_kfree(&pdev->dev, zone);
 204                        continue;
 205                }
 206
 207                zone->tzd = tzd;
 208                INIT_WORK(&zone->tz_device_update_work,
 209                          tz_device_update_work_fn);
 210
 211                tegra->zones[tegra->num_zones++] = zone;
 212        }
 213
 214        err = tegra_bpmp_request_mrq(bpmp, MRQ_THERMAL, bpmp_mrq_thermal,
 215                                     tegra);
 216        if (err) {
 217                dev_err(&pdev->dev, "failed to register mrq handler: %d\n",
 218                        err);
 219                return err;
 220        }
 221
 222        platform_set_drvdata(pdev, tegra);
 223
 224        return 0;
 225}
 226
 227static int tegra_bpmp_thermal_remove(struct platform_device *pdev)
 228{
 229        struct tegra_bpmp_thermal *tegra = platform_get_drvdata(pdev);
 230
 231        tegra_bpmp_free_mrq(tegra->bpmp, MRQ_THERMAL, tegra);
 232
 233        return 0;
 234}
 235
 236static const struct of_device_id tegra_bpmp_thermal_of_match[] = {
 237        { .compatible = "nvidia,tegra186-bpmp-thermal" },
 238        { },
 239};
 240MODULE_DEVICE_TABLE(of, tegra_bpmp_thermal_of_match);
 241
 242static struct platform_driver tegra_bpmp_thermal_driver = {
 243        .probe = tegra_bpmp_thermal_probe,
 244        .remove = tegra_bpmp_thermal_remove,
 245        .driver = {
 246                .name = "tegra-bpmp-thermal",
 247                .of_match_table = tegra_bpmp_thermal_of_match,
 248        },
 249};
 250module_platform_driver(tegra_bpmp_thermal_driver);
 251
 252MODULE_AUTHOR("Mikko Perttunen <mperttunen@nvidia.com>");
 253MODULE_DESCRIPTION("NVIDIA Tegra BPMP thermal sensor driver");
 254MODULE_LICENSE("GPL v2");
 255