12d71d8deSAmit Kucheria // SPDX-License-Identifier: GPL-2.0 29066073cSRajendra Nayak /* 39066073cSRajendra Nayak * Copyright (c) 2015, The Linux Foundation. All rights reserved. 49066073cSRajendra Nayak */ 59066073cSRajendra Nayak 67c938f48SAmit Kucheria #include <linux/debugfs.h> 79066073cSRajendra Nayak #include <linux/err.h> 89066073cSRajendra Nayak #include <linux/module.h> 99066073cSRajendra Nayak #include <linux/of.h> 10634e11d5SAmit Kucheria #include <linux/of_platform.h> 119066073cSRajendra Nayak #include <linux/platform_device.h> 129066073cSRajendra Nayak #include <linux/pm.h> 139066073cSRajendra Nayak #include <linux/slab.h> 149066073cSRajendra Nayak #include <linux/thermal.h> 159066073cSRajendra Nayak #include "tsens.h" 169066073cSRajendra Nayak 179066073cSRajendra Nayak static int tsens_get_temp(void *data, int *temp) 189066073cSRajendra Nayak { 198b71bce4SAmit Kucheria struct tsens_sensor *s = data; 2069b628acSAmit Kucheria struct tsens_priv *priv = s->priv; 219066073cSRajendra Nayak 228b71bce4SAmit Kucheria return priv->ops->get_temp(s, temp); 239066073cSRajendra Nayak } 249066073cSRajendra Nayak 252cbcd2eaSAmit Kucheria static int tsens_get_trend(void *data, int trip, enum thermal_trend *trend) 269066073cSRajendra Nayak { 278b71bce4SAmit Kucheria struct tsens_sensor *s = data; 2869b628acSAmit Kucheria struct tsens_priv *priv = s->priv; 299066073cSRajendra Nayak 3069b628acSAmit Kucheria if (priv->ops->get_trend) 318b71bce4SAmit Kucheria return priv->ops->get_trend(s, trend); 329066073cSRajendra Nayak 339066073cSRajendra Nayak return -ENOTSUPP; 349066073cSRajendra Nayak } 359066073cSRajendra Nayak 365b97469aSArnd Bergmann static int __maybe_unused tsens_suspend(struct device *dev) 379066073cSRajendra Nayak { 3869b628acSAmit Kucheria struct tsens_priv *priv = dev_get_drvdata(dev); 399066073cSRajendra Nayak 4069b628acSAmit Kucheria if (priv->ops && priv->ops->suspend) 4169b628acSAmit Kucheria return priv->ops->suspend(priv); 429066073cSRajendra Nayak 439066073cSRajendra Nayak return 0; 449066073cSRajendra Nayak } 459066073cSRajendra Nayak 465b97469aSArnd Bergmann static int __maybe_unused tsens_resume(struct device *dev) 479066073cSRajendra Nayak { 4869b628acSAmit Kucheria struct tsens_priv *priv = dev_get_drvdata(dev); 499066073cSRajendra Nayak 5069b628acSAmit Kucheria if (priv->ops && priv->ops->resume) 5169b628acSAmit Kucheria return priv->ops->resume(priv); 529066073cSRajendra Nayak 539066073cSRajendra Nayak return 0; 549066073cSRajendra Nayak } 559066073cSRajendra Nayak 569066073cSRajendra Nayak static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); 579066073cSRajendra Nayak 589066073cSRajendra Nayak static const struct of_device_id tsens_table[] = { 599066073cSRajendra Nayak { 609066073cSRajendra Nayak .compatible = "qcom,msm8916-tsens", 61840a5bd3SRajendra Nayak .data = &data_8916, 629066073cSRajendra Nayak }, { 639066073cSRajendra Nayak .compatible = "qcom,msm8974-tsens", 645e6703bdSRajendra Nayak .data = &data_8974, 65d059c739SRajendra Nayak }, { 660e580290SAngeloGioacchino Del Regno .compatible = "qcom,msm8976-tsens", 670e580290SAngeloGioacchino Del Regno .data = &data_8976, 680e580290SAngeloGioacchino Del Regno }, { 69d059c739SRajendra Nayak .compatible = "qcom,msm8996-tsens", 70d059c739SRajendra Nayak .data = &data_8996, 71191dc74bSAmit Kucheria }, { 72e8c24c6fSAmit Kucheria .compatible = "qcom,tsens-v1", 73e8c24c6fSAmit Kucheria .data = &data_tsens_v1, 74e8c24c6fSAmit Kucheria }, { 75191dc74bSAmit Kucheria .compatible = "qcom,tsens-v2", 76191dc74bSAmit Kucheria .data = &data_tsens_v2, 779066073cSRajendra Nayak }, 789066073cSRajendra Nayak {} 799066073cSRajendra Nayak }; 809066073cSRajendra Nayak MODULE_DEVICE_TABLE(of, tsens_table); 819066073cSRajendra Nayak 829066073cSRajendra Nayak static const struct thermal_zone_of_device_ops tsens_of_ops = { 839066073cSRajendra Nayak .get_temp = tsens_get_temp, 849066073cSRajendra Nayak .get_trend = tsens_get_trend, 85634e11d5SAmit Kucheria .set_trips = tsens_set_trips, 869066073cSRajendra Nayak }; 879066073cSRajendra Nayak 88*79125e03SAmit Kucheria static int tsens_register_irq(struct tsens_priv *priv, char *irqname, 89*79125e03SAmit Kucheria irq_handler_t thread_fn) 90*79125e03SAmit Kucheria { 91*79125e03SAmit Kucheria struct platform_device *pdev; 92*79125e03SAmit Kucheria int ret, irq; 93*79125e03SAmit Kucheria 94*79125e03SAmit Kucheria pdev = of_find_device_by_node(priv->dev->of_node); 95*79125e03SAmit Kucheria if (!pdev) 96*79125e03SAmit Kucheria return -ENODEV; 97*79125e03SAmit Kucheria 98*79125e03SAmit Kucheria irq = platform_get_irq_byname(pdev, irqname); 99*79125e03SAmit Kucheria if (irq < 0) { 100*79125e03SAmit Kucheria ret = irq; 101*79125e03SAmit Kucheria /* For old DTs with no IRQ defined */ 102*79125e03SAmit Kucheria if (irq == -ENXIO) 103*79125e03SAmit Kucheria ret = 0; 104*79125e03SAmit Kucheria } else { 105*79125e03SAmit Kucheria ret = devm_request_threaded_irq(&pdev->dev, irq, 106*79125e03SAmit Kucheria NULL, thread_fn, 107*79125e03SAmit Kucheria IRQF_TRIGGER_HIGH | IRQF_ONESHOT, 108*79125e03SAmit Kucheria dev_name(&pdev->dev), priv); 109*79125e03SAmit Kucheria if (ret) 110*79125e03SAmit Kucheria dev_err(&pdev->dev, "%s: failed to get irq\n", 111*79125e03SAmit Kucheria __func__); 112*79125e03SAmit Kucheria else 113*79125e03SAmit Kucheria enable_irq_wake(irq); 114*79125e03SAmit Kucheria } 115*79125e03SAmit Kucheria 116*79125e03SAmit Kucheria put_device(&pdev->dev); 117*79125e03SAmit Kucheria return ret; 118*79125e03SAmit Kucheria } 119*79125e03SAmit Kucheria 12069b628acSAmit Kucheria static int tsens_register(struct tsens_priv *priv) 1219066073cSRajendra Nayak { 122*79125e03SAmit Kucheria int i, ret; 1239066073cSRajendra Nayak struct thermal_zone_device *tzd; 1249066073cSRajendra Nayak 12569b628acSAmit Kucheria for (i = 0; i < priv->num_sensors; i++) { 12669b628acSAmit Kucheria priv->sensor[i].priv = priv; 1278b71bce4SAmit Kucheria tzd = devm_thermal_zone_of_sensor_register(priv->dev, priv->sensor[i].hw_id, 12869b628acSAmit Kucheria &priv->sensor[i], 1299066073cSRajendra Nayak &tsens_of_ops); 1309066073cSRajendra Nayak if (IS_ERR(tzd)) 1319066073cSRajendra Nayak continue; 13269b628acSAmit Kucheria priv->sensor[i].tzd = tzd; 13369b628acSAmit Kucheria if (priv->ops->enable) 13469b628acSAmit Kucheria priv->ops->enable(priv, i); 1359066073cSRajendra Nayak } 136634e11d5SAmit Kucheria 137*79125e03SAmit Kucheria ret = tsens_register_irq(priv, "uplow", tsens_irq_thread); 138*79125e03SAmit Kucheria if (ret < 0) 139*79125e03SAmit Kucheria return ret; 140634e11d5SAmit Kucheria 141*79125e03SAmit Kucheria if (priv->feat->crit_int) 142*79125e03SAmit Kucheria ret = tsens_register_irq(priv, "critical", 143*79125e03SAmit Kucheria tsens_critical_irq_thread); 144634e11d5SAmit Kucheria 145634e11d5SAmit Kucheria return ret; 1469066073cSRajendra Nayak } 1479066073cSRajendra Nayak 1489066073cSRajendra Nayak static int tsens_probe(struct platform_device *pdev) 1499066073cSRajendra Nayak { 1509066073cSRajendra Nayak int ret, i; 1519066073cSRajendra Nayak struct device *dev; 1529066073cSRajendra Nayak struct device_node *np; 15369b628acSAmit Kucheria struct tsens_priv *priv; 1543c040ce0SAmit Kucheria const struct tsens_plat_data *data; 1559066073cSRajendra Nayak const struct of_device_id *id; 1566d7c70d1SBjorn Andersson u32 num_sensors; 1579066073cSRajendra Nayak 1589066073cSRajendra Nayak if (pdev->dev.of_node) 1599066073cSRajendra Nayak dev = &pdev->dev; 1609066073cSRajendra Nayak else 1619066073cSRajendra Nayak dev = pdev->dev.parent; 1629066073cSRajendra Nayak 1639066073cSRajendra Nayak np = dev->of_node; 1649066073cSRajendra Nayak 1659066073cSRajendra Nayak id = of_match_node(tsens_table, np); 16620d4fd84SRajendra Nayak if (id) 1679066073cSRajendra Nayak data = id->data; 16820d4fd84SRajendra Nayak else 16920d4fd84SRajendra Nayak data = &data_8960; 1709066073cSRajendra Nayak 1716d7c70d1SBjorn Andersson num_sensors = data->num_sensors; 1726d7c70d1SBjorn Andersson 1736d7c70d1SBjorn Andersson if (np) 1746d7c70d1SBjorn Andersson of_property_read_u32(np, "#qcom,sensors", &num_sensors); 1756d7c70d1SBjorn Andersson 1766d7c70d1SBjorn Andersson if (num_sensors <= 0) { 1773795ad5eSAmit Kucheria dev_err(dev, "%s: invalid number of sensors\n", __func__); 1789066073cSRajendra Nayak return -EINVAL; 1799066073cSRajendra Nayak } 1809066073cSRajendra Nayak 18169b628acSAmit Kucheria priv = devm_kzalloc(dev, 18269b628acSAmit Kucheria struct_size(priv, sensor, num_sensors), 1830ed2dd03SKees Cook GFP_KERNEL); 18469b628acSAmit Kucheria if (!priv) 1859066073cSRajendra Nayak return -ENOMEM; 1869066073cSRajendra Nayak 18769b628acSAmit Kucheria priv->dev = dev; 18869b628acSAmit Kucheria priv->num_sensors = num_sensors; 18969b628acSAmit Kucheria priv->ops = data->ops; 19069b628acSAmit Kucheria for (i = 0; i < priv->num_sensors; i++) { 1919066073cSRajendra Nayak if (data->hw_ids) 19269b628acSAmit Kucheria priv->sensor[i].hw_id = data->hw_ids[i]; 1939066073cSRajendra Nayak else 19469b628acSAmit Kucheria priv->sensor[i].hw_id = i; 1959066073cSRajendra Nayak } 196c1997054SAmit Kucheria priv->feat = data->feat; 197c1997054SAmit Kucheria priv->fields = data->fields; 1989066073cSRajendra Nayak 1990e9c0bc7SAmit Kucheria platform_set_drvdata(pdev, priv); 2000e9c0bc7SAmit Kucheria 20169b628acSAmit Kucheria if (!priv->ops || !priv->ops->init || !priv->ops->get_temp) 2029066073cSRajendra Nayak return -EINVAL; 2039066073cSRajendra Nayak 20469b628acSAmit Kucheria ret = priv->ops->init(priv); 2059066073cSRajendra Nayak if (ret < 0) { 2063795ad5eSAmit Kucheria dev_err(dev, "%s: init failed\n", __func__); 2079066073cSRajendra Nayak return ret; 2089066073cSRajendra Nayak } 2099066073cSRajendra Nayak 21069b628acSAmit Kucheria if (priv->ops->calibrate) { 21169b628acSAmit Kucheria ret = priv->ops->calibrate(priv); 2129066073cSRajendra Nayak if (ret < 0) { 213fc7d18cfSAmit Kucheria if (ret != -EPROBE_DEFER) 2143795ad5eSAmit Kucheria dev_err(dev, "%s: calibration failed\n", __func__); 2159066073cSRajendra Nayak return ret; 2169066073cSRajendra Nayak } 2179066073cSRajendra Nayak } 2189066073cSRajendra Nayak 2190e9c0bc7SAmit Kucheria return tsens_register(priv); 2209066073cSRajendra Nayak } 2219066073cSRajendra Nayak 2229066073cSRajendra Nayak static int tsens_remove(struct platform_device *pdev) 2239066073cSRajendra Nayak { 22469b628acSAmit Kucheria struct tsens_priv *priv = platform_get_drvdata(pdev); 2259066073cSRajendra Nayak 2267c938f48SAmit Kucheria debugfs_remove_recursive(priv->debug_root); 227634e11d5SAmit Kucheria tsens_disable_irq(priv); 22869b628acSAmit Kucheria if (priv->ops->disable) 22969b628acSAmit Kucheria priv->ops->disable(priv); 2309066073cSRajendra Nayak 2319066073cSRajendra Nayak return 0; 2329066073cSRajendra Nayak } 2339066073cSRajendra Nayak 2349066073cSRajendra Nayak static struct platform_driver tsens_driver = { 2359066073cSRajendra Nayak .probe = tsens_probe, 2369066073cSRajendra Nayak .remove = tsens_remove, 2379066073cSRajendra Nayak .driver = { 2389066073cSRajendra Nayak .name = "qcom-tsens", 2399066073cSRajendra Nayak .pm = &tsens_pm_ops, 2409066073cSRajendra Nayak .of_match_table = tsens_table, 2419066073cSRajendra Nayak }, 2429066073cSRajendra Nayak }; 2439066073cSRajendra Nayak module_platform_driver(tsens_driver); 2449066073cSRajendra Nayak 2459066073cSRajendra Nayak MODULE_LICENSE("GPL v2"); 2469066073cSRajendra Nayak MODULE_DESCRIPTION("QCOM Temperature Sensor driver"); 2479066073cSRajendra Nayak MODULE_ALIAS("platform:qcom-tsens"); 248